Merged revisions 2454-2525 via svnmerge from
[scons.git] / src / engine / SCons / Options / __init__.py
1 """engine.SCons.Options
2
3 This file defines the Options class that is used to add user-friendly
4 customizable variables to an SCons build.
5 """
6
7 #
8 # __COPYRIGHT__
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 import SCons.compat
33
34 import os.path
35 import string
36 import sys
37
38 import SCons.Errors
39 import SCons.Util
40 import SCons.Warnings
41
42 from BoolOption import BoolOption  # okay
43 from EnumOption import EnumOption  # okay
44 from ListOption import ListOption  # naja
45 from PackageOption import PackageOption # naja
46 from PathOption import PathOption # okay
47
48
49 class Options:
50     instance=None
51
52     """
53     Holds all the options, updates the environment with the variables,
54     and renders the help text.
55     """
56     def __init__(self, files=[], args={}, is_global=1):
57         """
58         files - [optional] List of option configuration files to load
59             (backward compatibility) If a single string is passed it is
60                                      automatically placed in a file list
61         """
62         self.options = []
63         self.args = args
64         if not SCons.Util.is_List(files):
65             if files:
66                 files = [ files ]
67             else:
68                 files = []
69         self.files = files
70         self.unknown = {}
71
72         # create the singleton instance
73         if is_global:
74             self=Options.instance
75
76             if not Options.instance:
77                 Options.instance=self
78
79     def _do_add(self, key, help="", default=None, validator=None, converter=None):
80         class Option:
81             pass
82
83         option = Option()
84
85         # if we get a list or a tuple, we take the first element as the
86         # option key and store the remaining in aliases.
87         if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key):
88           option.key     = key[0]
89           option.aliases = key[1:]
90         else:
91           option.key     = key
92           option.aliases = [ key ]
93         option.help = help
94         option.default = default
95         option.validator = validator
96         option.converter = converter
97
98         self.options.append(option)
99
100     def keys(self):
101         """
102         Returns the keywords for the options
103         """
104         return map(lambda o: o.key, self.options)
105
106     def Add(self, key, help="", default=None, validator=None, converter=None, **kw):
107         """
108         Add an option.
109
110         key - the name of the variable, or a list or tuple of arguments
111         help - optional help text for the options
112         default - optional default value
113         validator - optional function that is called to validate the option's value
114                     Called with (key, value, environment)
115         converter - optional function that is called to convert the option's value before
116                     putting it in the environment.
117         """
118
119         if SCons.Util.is_List(key) or type(key) == type(()):
120             apply(self._do_add, key)
121             return
122
123         if not SCons.Util.is_String(key) or \
124            not SCons.Util.is_valid_construction_var(key):
125             raise SCons.Errors.UserError, "Illegal Options.Add() key `%s'" % str(key)
126
127         self._do_add(key, help, default, validator, converter)
128
129     def AddOptions(self, *optlist):
130         """
131         Add a list of options.
132
133         Each list element is a tuple/list of arguments to be passed on
134         to the underlying method for adding options.
135
136         Example:
137           opt.AddOptions(
138             ('debug', '', 0),
139             ('CC', 'The C compiler'),
140             ('VALIDATE', 'An option for testing validation', 'notset',
141              validator, None),
142             )
143         """
144         for o in optlist:
145             apply(self._do_add, o)
146
147
148     def Update(self, env, args=None):
149         """
150         Update an environment with the option variables.
151
152         env - the environment to update.
153         """
154
155         values = {}
156
157         # first set the defaults:
158         for option in self.options:
159             if not option.default is None:
160                 values[option.key] = option.default
161
162         # next set the value specified in the options file
163         for filename in self.files:
164             if os.path.exists(filename):
165                 dir = os.path.split(os.path.abspath(filename))[0]
166                 if dir:
167                     sys.path.insert(0, dir)
168                 try:
169                     values['__name__'] = filename
170                     execfile(filename, {}, values)
171                 finally:
172                     if dir:
173                         del sys.path[0]
174                     del values['__name__']
175
176         # set the values specified on the command line
177         if args is None:
178             args = self.args
179
180         for arg, value in args.items():
181             added = False
182             for option in self.options:
183                 if arg in option.aliases + [ option.key ]:
184                     values[option.key] = value
185                     added = True
186             if not added:
187                 self.unknown[arg] = value
188
189         # put the variables in the environment:
190         # (don't copy over variables that are not declared as options)
191         for option in self.options:
192             try:
193                 env[option.key] = values[option.key]
194             except KeyError:
195                 pass
196
197         # Call the convert functions:
198         for option in self.options:
199             if option.converter and values.has_key(option.key):
200                 value = env.subst('${%s}'%option.key)
201                 try:
202                     try:
203                         env[option.key] = option.converter(value)
204                     except TypeError:
205                         env[option.key] = option.converter(value, env)
206                 except ValueError, x:
207                     raise SCons.Errors.UserError, 'Error converting option: %s\n%s'%(option.key, x)
208
209
210         # Finally validate the values:
211         for option in self.options:
212             if option.validator and values.has_key(option.key):
213                 option.validator(option.key, env.subst('${%s}'%option.key), env)
214
215     def UnknownOptions(self):
216         """
217         Returns any options in the specified arguments lists that
218         were not known, declared options in this object.
219         """
220         return self.unknown
221
222     def Save(self, filename, env):
223         """
224         Saves all the options in the given file.  This file can
225         then be used to load the options next run.  This can be used
226         to create an option cache file.
227
228         filename - Name of the file to save into
229         env - the environment get the option values from
230         """
231
232         # Create the file and write out the header
233         try:
234             fh = open(filename, 'w')
235
236             try:
237                 # Make an assignment in the file for each option within the environment
238                 # that was assigned a value other than the default.
239                 for option in self.options:
240                     try:
241                         value = env[option.key]
242                         try:
243                             eval(repr(value))
244                         except KeyboardInterrupt:
245                             raise
246                         except:
247                             # Convert stuff that has a repr() that
248                             # cannot be evaluated into a string
249                             value = SCons.Util.to_String(value)
250
251                         defaultVal = env.subst(SCons.Util.to_String(option.default))
252                         if option.converter:
253                             defaultVal = option.converter(defaultVal)
254
255                         if str(env.subst('${%s}' % option.key)) != str(defaultVal):
256                             fh.write('%s = %s\n' % (option.key, repr(value)))
257                     except KeyError:
258                         pass
259             finally:
260                 fh.close()
261
262         except IOError, x:
263             raise SCons.Errors.UserError, 'Error writing options to file: %s\n%s' % (filename, x)
264
265     def GenerateHelpText(self, env, sort=None):
266         """
267         Generate the help text for the options.
268
269         env - an environment that is used to get the current values
270               of the options.
271         """
272
273         if sort:
274             options = self.options[:]
275             options.sort(lambda x,y,func=sort: func(x.key,y.key))
276         else:
277             options = self.options
278
279         def format(opt, self=self, env=env):
280             if env.has_key(opt.key):
281                 actual = env.subst('${%s}' % opt.key)
282             else:
283                 actual = None
284             return self.FormatOptionHelpText(env, opt.key, opt.help, opt.default, actual, opt.aliases)
285         lines = filter(None, map(format, options))
286
287         return string.join(lines, '')
288
289     format  = '\n%s: %s\n    default: %s\n    actual: %s\n'
290     format_ = '\n%s: %s\n    default: %s\n    actual: %s\n    aliases: %s\n'
291
292     def FormatOptionHelpText(self, env, key, help, default, actual, aliases=[]):
293         # Don't display the key name itself as an alias.
294         aliases = filter(lambda a, k=key: a != k, aliases)
295         if len(aliases)==0:
296             return self.format % (key, help, default, actual)
297         else:
298             return self.format_ % (key, help, default, actual, aliases)
299