env2 = env.Copy(CC="cl.exe")
.EE
+.SS Costruction Variable Options
+
+Often when building software, various options need to be specified at build
+time that are not known when the SConstruct/SConscript files are
+written. For example, libraries needed for the build may be in non-standard
+locations, or site-specific compiler options may need to be passed to the
+compiler.
+.B scons
+provides a mechanism for overridding construction variables from the
+command line or a text based configuration file through an Options
+object. To create an Options object, call the Options() function:
+
+.TP
+.RI Options([ file ])
+This creates an Options object that will read construction variables from
+the filename based in the
+.I file
+argument. If no filename is given, then no file will be read. Example:
+
+.ES
+opts = Options('custom.py')
+.EE
+
+Options objects have the following methods:
+
+.TP
+.RI Add( key ", [" help ", " default ", " validater ", " converter ])
+This adds a customizable construction variable to the Options object.
+.I key
+is the name of the variable.
+.I help
+is the help text for the variable.
+.I default
+is the default value of the variable.
+.I validater
+is called to validate the value of the variable, and should take two
+arguments: key and value.
+.I converter
+is called to convert the value before putting it in the environment, and
+should take a single argument: value. Example:
+
+.ES
+opts.Add('CC', 'The C compiler')
+.EE
+
+.TP
+.RI Update( env )
+This updates a construction environment
+.I env
+with the customized construction variables. Normally this method is not
+called directly, but is called indirectly by passing the Options object to
+the Environment() function:
+
+.ES
+env = Environment(options=opts)
+.EE
+
+.TP
+.RI GenerateHelpText( env )
+This generates help text documenting the customizable construction
+variables suitable to passing in to the Help() function.
+.I env
+is the construction environment that will be used to get the actual values
+of customizable variables. Example:
+
+.ES
+Help(opts.GenerateHelpText(env))
+.EE
+
+The text based configuration file is executed as a Python script, and the
+global variables are queried for customizable construction
+variables. Example:
+
+.ES
+CC = 'my_cc'
+.EE
+
.SS Other Functions
.B scons
(for example, 'liba.a' on POSIX systems,
'a.lib' on Windows).
+.SS Customizing contruction variables from the command line.
+
+The following would allow the C compiler to be specified on the command
+line or in the file custom.py.
+
+.ES
+opts = Options('custom.py')
+opts.Add('CC', 'The C compiler.')
+env = Environment(options=opts)
+Help(opts.GenerateHelpText(env))
+.EE
+
+The user could specify the C compiler on the command line:
+
+.ES
+scons "CC=my_cc"
+.EE
+
+or in the custom.py file:
+
+.ES
+CC = 'my_cc'
+.EE
+
+or get documentation on the options:
+
+.ES
+> scons -h
+
+CC: The C compiler.
+ default: None
+ actual: cc
+
+.EE
+
.SH ENVIRONMENT
.IP SCONS_LIB_DIR
- Fix use of -j with multiple targets.
+ - Add an Options() object for friendlier accomodation of command-
+ line arguments.
+
From sam th:
- Dynamically check for the existence of utilities with which to
SCons/Node/__init__.py
SCons/Node/Alias.py
SCons/Node/FS.py
+SCons/Options.py
SCons/Platform/__init__.py
SCons/Platform/cygwin.py
SCons/Platform/os2.py
def __init__(self,
platform=SCons.Platform.Platform(),
tools=None,
+ options=None,
**kw):
self.fs = SCons.Node.FS.default_fs
self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
+
if SCons.Util.is_String(platform):
platform = SCons.Platform.Platform(platform)
platform(self)
+
+ # Apply the passed-in variables before calling the tools,
+ # because they may use some of them:
+ apply(self.Replace, (), kw)
+
+ # Update the environment with the customizable options
+ # before calling the tools, since they may use some of the options:
+ if options:
+ options.Update(self)
+
if tools is None:
tools = ['default']
- apply(self.Replace, (), kw)
for tool in tools:
if SCons.Util.is_String(tool):
tool = SCons.Tool.Tool(tool)
tool(self, platform)
+
+ # Reapply the passed in variables after calling the tools,
+ # since they should overide anything set by the tools:
apply(self.Replace, (), kw)
+ # Update the environment with the customizable options
+ # after calling the tools, since they should override anything
+ # set by the tools:
+ if options:
+ options.Update(self)
+
#
# self.autogen_vars is a tuple of tuples. Each inner tuple
# has four elements, each strings referring to an environment
--- /dev/null
+"""engine.SCons.Options
+
+This file defines the Options class that is used to add user-friendly customizable
+variables to a scons build.
+"""
+
+#
+# Copyright (c) 2001, 2002 Steven Knight
+#
+# 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 SCons.Errors
+import os.path
+
+
+class Options:
+ """
+ Holds all the options, updates the environment with the variables,
+ and renders the help text.
+ """
+ def __init__(self, file=None):
+ """
+ file - [optional] the name of the customizable file.
+ """
+
+ self.options = []
+ self.file = file
+
+ def Add(self, key, help="", default=None, validater=None, converter=None):
+ """
+ Add an option.
+
+ key - the name of the variable
+ help - optional help text for the options
+ default - optional default value
+ validater - optional function that is called to validate the option's value
+ converter - optional function that is called to convert the option's value before
+ putting it in the environment.
+ """
+
+ class Option:
+ pass
+
+ option = Option()
+ option.key = key
+ option.help = help
+ option.default = default
+ option.validater = validater
+ option.converter = converter
+
+ self.options.append(option)
+
+ def Update(self, env, args):
+ """
+ Update an environment with the option variables.
+
+ env - the environment to update.
+ args - the dictionary to get the command line arguments from.
+ """
+
+ values = {}
+
+ # first set the defaults:
+ for option in self.options:
+ if not option.default is None:
+ values[option.key] = option.default
+
+ # next set the value specified in the options file
+ if self.file and os.path.exists(self.file):
+ execfile(self.file, values)
+
+ # finally set the values specified on the command line
+ values.update(args)
+
+ # put the variables in the environment:
+ for key in values.keys():
+ env[key] = values[key]
+
+ # Call the convert functions:
+ for option in self.options:
+ if option.converter:
+ value = env.subst('${%s}'%option.key)
+ try:
+ env[option.key] = option.converter(value)
+ except ValueError, x:
+ raise SCons.Errors.UserError, 'Error converting option: %s\n%s'%(options.key, x)
+
+
+ # Finally validate the values:
+ for option in self.options:
+ if option.validater:
+ option.validater(option.key, env.subst('${%s}'%option.key))
+
+
+ def GenerateHelpText(self, env):
+ """
+ Generate the help text for the options.
+
+ env - an environment that is used to get the current values of the options.
+ """
+
+ help_text = ""
+
+ for option in self.options:
+ help_text = help_text + '\n%s: %s\n default: %s\n actual: %s\n'%(option.key, option.help, option.default, env.subst('${%s}'%option.key))
+
+ return help_text
+
--- /dev/null
+#
+# Copyright (c) 2001, 2002 Steven Knight
+#
+# 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 unittest
+import TestSCons
+import SCons.Options
+import sys
+import string
+
+class Environment:
+ def __init__(self):
+ self.dict = {}
+ def subst(self, x):
+ return self.dict[x[2:-1]]
+ def __setitem__(self, key, value):
+ self.dict[key] = value
+ def __getitem__(self, key):
+ return self.dict[key]
+
+
+def check(key,value):
+ assert value == 6 * 9,key
+
+class OptionsTestCase(unittest.TestCase):
+ def test_Add(self):
+ opts = SCons.Options.Options()
+
+ opts.Add('VAR')
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "42",
+ check,
+ lambda x: int(x) + 12)
+
+ o = opts.options[0]
+ assert o.key == 'VAR'
+ assert o.help == ''
+ assert o.default == None
+ assert o.validater == None
+ assert o.converter == None
+
+ o = opts.options[1]
+ assert o.key == 'ANSWER'
+ assert o.help == 'THE answer to THE question'
+ assert o.default == "42"
+ o.validater(o.key, o.converter(o.default))
+
+ def test_Update(self):
+
+ test = TestSCons.TestSCons()
+ file = test.workpath('custom.py')
+ opts = SCons.Options.Options(file)
+
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "42",
+ check,
+ lambda x: int(x) + 12)
+
+ env = Environment()
+ opts.Update(env, {})
+ assert env['ANSWER'] == 54
+
+ test = TestSCons.TestSCons()
+ file = test.workpath('custom.py')
+ test.write('custom.py', 'ANSWER=54')
+ opts = SCons.Options.Options(file)
+
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "42",
+ check,
+ lambda x: int(x) + 12)
+
+ env = Environment()
+ try:
+ opts.Update(env, {})
+ except AssertionError:
+ pass
+
+ test = TestSCons.TestSCons()
+ file = test.workpath('custom.py')
+ test.write('custom.py', 'ANSWER=42')
+ opts = SCons.Options.Options(file)
+
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "54",
+ check,
+ lambda x: int(x) + 12)
+
+ env = Environment()
+ opts.Update(env, {})
+ assert env['ANSWER'] == 54
+
+ test = TestSCons.TestSCons()
+ file = test.workpath('custom.py')
+ test.write('custom.py', 'ANSWER=54')
+ opts = SCons.Options.Options(file)
+
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "54",
+ check,
+ lambda x: int(x) + 12)
+
+ env = Environment()
+ opts.Update(env, {'ANSWER':'42'})
+ assert env['ANSWER'] == 54
+
+ def test_GenerateHelpText(self):
+ opts = SCons.Options.Options()
+
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "42",
+ check,
+ lambda x: int(x) + 12)
+
+ env = Environment()
+ opts.Update(env, {})
+
+ expect = """
+ANSWER: THE answer to THE question
+ default: 42
+ actual: 54
+"""
+
+ text = opts.GenerateHelpText(env)
+ assert text == expect, text
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(OptionsTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
import SCons.Tool
import SCons.Util
import SCons.Sig
+import SCons.Options
import os
import os.path
else:
raise SCons.Errors.UserError, "Unknown build signature type '%s'"%type
+class Options(SCons.Options.Options):
+ def Update(self, env):
+ return SCons.Options.Options.Update(self, env, arguments)
+
def BuildDefaultGlobals():
"""
Create a dictionary containing all the default globals for
globals['Split'] = SCons.Util.Split
globals['Tool'] = SCons.Tool.Tool
globals['WhereIs'] = SCons.Util.WhereIs
+ globals['Options'] = Options
return globals
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (c) 2001, 2002 Steven Knight
+#
+# 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 TestSCons
+import string
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment()
+print env['CC']
+print env['CCFLAGS']
+Default(env.Alias('dummy'))
+""")
+test.run()
+cc, ccflags = string.split(test.stdout(), '\n')[:2]
+
+test.write('SConstruct', """
+opts = Options('custom.py')
+opts.Add('RELEASE_BUILD',
+ 'Set to 1 to build a release build',
+ 0,
+ None,
+ int)
+
+opts.Add('DEBUG_BUILD',
+ 'Set to 1 to build a debug build',
+ 1,
+ None,
+ int)
+
+opts.Add('CC',
+ 'The C compiler')
+
+def test_tool(env, platform):
+ if env['RELEASE_BUILD']:
+ env['CCFLAGS'] = env['CCFLAGS'] + ' -O'
+ if env['DEBUG_BUILD']:
+ env['CCFLAGS'] = env['CCFLAGS'] + ' -g'
+
+
+env = Environment(options=opts, tools=['default', test_tool])
+
+Help('Variables settable in custom.py or on the command line:\\n' + opts.GenerateHelpText(env))
+
+print env['RELEASE_BUILD']
+print env['DEBUG_BUILD']
+print env['CC']
+print env['CCFLAGS']
+
+Default(env.Alias('dummy'))
+
+""")
+
+def check(expect):
+ result = string.split(test.stdout(), '\n')
+ assert result[0:len(expect)] == expect, (result[0:len(expect)], expect)
+
+test.run()
+check(['0', '1', cc, ccflags + ' -g'])
+
+test.run(arguments='"RELEASE_BUILD=1"')
+check(['1', '1', cc, ccflags + ' -O -g'])
+
+test.run(arguments='"RELEASE_BUILD=1" "DEBUG_BUILD=0"')
+check(['1', '0', cc, ccflags + ' -O'])
+
+test.run(arguments='"CC=not_a_c_compiler"')
+check(['0', '1', 'not_a_c_compiler', ccflags + ' -g'])
+
+test.write('custom.py', """
+DEBUG_BUILD=0
+RELEASE_BUILD=1
+""")
+
+test.run()
+check(['1', '0', cc, ccflags + ' -O'])
+
+test.run(arguments='"DEBUG_BUILD=1"')
+check(['1', '1', cc, ccflags + ' -O -g'])
+
+test.run(arguments='-h')
+assert test.stdout() == """Variables settable in custom.py or on the command line:
+
+RELEASE_BUILD: Set to 1 to build a release build
+ default: 0
+ actual: 1
+
+DEBUG_BUILD: Set to 1 to build a debug build
+ default: 1
+ actual: 0
+
+CC: The C compiler
+ default: None
+ actual: %s
+
+Use scons -H for help about command-line options.
+"""%cc
+
+test.pass_test()