09d4e29af7948cdacb23cb7d224b74994739c0a9
[scons.git] / src / engine / SCons / Variables / __init__.py
1 """engine.SCons.Variables
2
3 This file defines the Variables 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 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
30
31 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
32
33 import os.path
34 import sys
35
36 import SCons.Environment
37 import SCons.Errors
38 import SCons.Util
39 import SCons.Warnings
40
41 from BoolVariable import BoolVariable  # okay
42 from EnumVariable import EnumVariable  # okay
43 from ListVariable import ListVariable  # naja
44 from PackageVariable import PackageVariable # naja
45 from PathVariable import PathVariable # okay
46
47
48 class Variables:
49     instance=None
50
51     """
52     Holds all the options, updates the environment with the variables,
53     and renders the help text.
54     """
55     def __init__(self, files=[], args={}, is_global=1):
56         """
57         files - [optional] List of option configuration files to load
58             (backward compatibility) If a single string is passed it is
59                                      automatically placed in a file list
60         """
61         self.options = []
62         self.args = args
63         if not SCons.Util.is_List(files):
64             if files:
65                 files = [ files ]
66             else:
67                 files = []
68         self.files = files
69         self.unknown = {}
70
71         # create the singleton instance
72         if is_global:
73             self=Variables.instance
74
75             if not Variables.instance:
76                 Variables.instance=self
77
78     def _do_add(self, key, help="", default=None, validator=None, converter=None):
79         class Variable:
80             pass
81
82         option = Variable()
83
84         # if we get a list or a tuple, we take the first element as the
85         # option key and store the remaining in aliases.
86         if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key):
87           option.key     = key[0]
88           option.aliases = key[1:]
89         else:
90           option.key     = key
91           option.aliases = [ key ]
92         option.help = help
93         option.default = default
94         option.validator = validator
95         option.converter = converter
96
97         self.options.append(option)
98         
99         # options might be added after the 'unknown' dict has been set up,
100         # so we remove the key and all its aliases from that dict
101         for alias in list(option.aliases) + [ option.key ]:
102           # TODO(1.5)
103           #if alias in self.unknown:
104           if alias in self.unknown.keys():
105             del self.unknown[alias]
106
107     def keys(self):
108         """
109         Returns the keywords for the options
110         """
111         return [o.key for o in self.options]
112
113     def Add(self, key, help="", default=None, validator=None, converter=None, **kw):
114         """
115         Add an option.
116
117         key - the name of the variable, or a list or tuple of arguments
118         help - optional help text for the options
119         default - optional default value
120         validator - optional function that is called to validate the option's value
121                     Called with (key, value, environment)
122         converter - optional function that is called to convert the option's value before
123                     putting it in the environment.
124         """
125
126         if SCons.Util.is_List(key) or isinstance(key, tuple):
127             self._do_add(*key)
128             return
129
130         if not SCons.Util.is_String(key) or \
131            not SCons.Environment.is_valid_construction_var(key):
132             raise SCons.Errors.UserError, "Illegal Variables.Add() key `%s'" % str(key)
133
134         self._do_add(key, help, default, validator, converter)
135
136     def AddVariables(self, *optlist):
137         """
138         Add a list of options.
139
140         Each list element is a tuple/list of arguments to be passed on
141         to the underlying method for adding options.
142
143         Example:
144           opt.AddVariables(
145             ('debug', '', 0),
146             ('CC', 'The C compiler'),
147             ('VALIDATE', 'An option for testing validation', 'notset',
148              validator, None),
149             )
150         """
151         for o in optlist:
152             self._do_add(*o)
153
154
155     def Update(self, env, args=None):
156         """
157         Update an environment with the option variables.
158
159         env - the environment to update.
160         """
161
162         values = {}
163
164         # first set the defaults:
165         for option in self.options:
166             if not option.default is None:
167                 values[option.key] = option.default
168
169         # next set the value specified in the options file
170         for filename in self.files:
171             if os.path.exists(filename):
172                 dir = os.path.split(os.path.abspath(filename))[0]
173                 if dir:
174                     sys.path.insert(0, dir)
175                 try:
176                     values['__name__'] = filename
177                     exec open(filename, 'rU').read() in {}, values
178                 finally:
179                     if dir:
180                         del sys.path[0]
181                     del values['__name__']
182
183         # set the values specified on the command line
184         if args is None:
185             args = self.args
186
187         for arg, value in args.items():
188             added = False
189             for option in self.options:
190                 if arg in list(option.aliases) + [ option.key ]:
191                     values[option.key] = value
192                     added = True
193             if not added:
194                 self.unknown[arg] = value
195
196         # put the variables in the environment:
197         # (don't copy over variables that are not declared as options)
198         for option in self.options:
199             try:
200                 env[option.key] = values[option.key]
201             except KeyError:
202                 pass
203
204         # Call the convert functions:
205         for option in self.options:
206             if option.converter and option.key in values:
207                 value = env.subst('${%s}'%option.key)
208                 try:
209                     try:
210                         env[option.key] = option.converter(value)
211                     except TypeError:
212                         env[option.key] = option.converter(value, env)
213                 except ValueError, x:
214                     raise SCons.Errors.UserError, 'Error converting option: %s\n%s'%(option.key, x)
215
216
217         # Finally validate the values:
218         for option in self.options:
219             if option.validator and option.key in values:
220                 option.validator(option.key, env.subst('${%s}'%option.key), env)
221
222     def UnknownVariables(self):
223         """
224         Returns any options in the specified arguments lists that
225         were not known, declared options in this object.
226         """
227         return self.unknown
228
229     def Save(self, filename, env):
230         """
231         Saves all the options in the given file.  This file can
232         then be used to load the options next run.  This can be used
233         to create an option cache file.
234
235         filename - Name of the file to save into
236         env - the environment get the option values from
237         """
238
239         # Create the file and write out the header
240         try:
241             fh = open(filename, 'w')
242
243             try:
244                 # Make an assignment in the file for each option
245                 # within the environment that was assigned a value
246                 # other than the default.
247                 for option in self.options:
248                     try:
249                         value = env[option.key]
250                         try:
251                             prepare = value.prepare_to_store
252                         except AttributeError:
253                             try:
254                                 eval(repr(value))
255                             except KeyboardInterrupt:
256                                 raise
257                             except:
258                                 # Convert stuff that has a repr() that
259                                 # cannot be evaluated into a string
260                                 value = SCons.Util.to_String(value)
261                         else:
262                             value = prepare()
263
264                         defaultVal = env.subst(SCons.Util.to_String(option.default))
265                         if option.converter:
266                             defaultVal = option.converter(defaultVal)
267
268                         if str(env.subst('${%s}' % option.key)) != str(defaultVal):
269                             fh.write('%s = %s\n' % (option.key, repr(value)))
270                     except KeyError:
271                         pass
272             finally:
273                 fh.close()
274
275         except IOError, x:
276             raise SCons.Errors.UserError, 'Error writing options to file: %s\n%s' % (filename, x)
277
278     def GenerateHelpText(self, env, sort=None):
279         """
280         Generate the help text for the options.
281
282         env - an environment that is used to get the current values
283               of the options.
284         """
285
286         if sort:
287             options = sorted(self.options, cmp=lambda x,y: sort(x.key,y.key))
288         else:
289             options = self.options
290
291         def format(opt, self=self, env=env):
292             if opt.key in env:
293                 actual = env.subst('${%s}' % opt.key)
294             else:
295                 actual = None
296             return self.FormatVariableHelpText(env, opt.key, opt.help, opt.default, actual, opt.aliases)
297         lines = [_f for _f in map(format, options) if _f]
298
299         return ''.join(lines)
300
301     format  = '\n%s: %s\n    default: %s\n    actual: %s\n'
302     format_ = '\n%s: %s\n    default: %s\n    actual: %s\n    aliases: %s\n'
303
304     def FormatVariableHelpText(self, env, key, help, default, actual, aliases=[]):
305         # Don't display the key name itself as an alias.
306         aliases = [a for a in aliases if a != key]
307         if len(aliases)==0:
308             return self.format % (key, help, default, actual)
309         else:
310             return self.format_ % (key, help, default, actual, aliases)
311
312 # Local Variables:
313 # tab-width:4
314 # indent-tabs-mode:nil
315 # End:
316 # vim: set expandtab tabstop=4 shiftwidth=4: