Make construction variables with a value of 0 work (Anthony Roach)
[scons.git] / src / engine / SCons / Util.py
1 """SCons.Util
2
3 Various utility functions go here.
4
5 """
6
7 #
8 # Copyright (c) 2001, 2002 Steven Knight
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
33 import copy
34 import os.path
35 import re
36 import string
37 import types
38 import UserDict
39 import UserList
40
41 try:
42     from UserString import UserString
43 except ImportError:
44     class UserString:
45         pass
46
47 import SCons.Node
48 import SCons.Node.FS
49
50 def scons_str2nodes(arg, node_factory=SCons.Node.FS.default_fs.File):
51     """This function converts a string or list into a list of Node instances.
52     It follows the rules outlined in the SCons design document by accepting
53     any of the following inputs:
54         - A single string containing names separated by spaces. These will be
55           split apart at the spaces.
56         - A single Node instance,
57         - A list containingg either strings or Node instances. Any strings
58           in the list are not split at spaces.
59     In all cases, the function returns a list of Node instances."""
60
61     narg = arg
62     if is_String(arg):
63         narg = string.split(arg)
64     elif not is_List(arg):
65         narg = [arg]
66
67     nodes = []
68     for v in narg:
69         if is_String(v):
70             nodes.append(node_factory(v))
71         # Do we enforce the following restriction?  Maybe, but it
72         # also restricts what we can do for allowing people to
73         # use the engine with alternate Node implementations...
74         # Perhaps this should be split in two, with the SCons.Node
75         # logic in a wrapper somewhere under SCons.Node, and the
76         # string-parsing logic here...?
77         #elif not issubclass(v.__class__, SCons.Node.Node):
78         #    raise TypeError
79         else:
80             nodes.append(v)
81
82     return nodes
83
84
85 class PathList(UserList.UserList):
86     """This class emulates the behavior of a list, but also implements
87     the special "path dissection" attributes we can use to find
88     suffixes, base names, etc. of the paths in the list.
89
90     One other special attribute of this class is that, by
91     overriding the __str__ and __repr__ methods, this class
92     represents itself as a space-concatenated string of
93     the list elements, as in:
94
95     >>> pl=PathList(["/foo/bar.txt", "/baz/foo.txt"])
96     >>> pl
97     '/foo/bar.txt /baz/foo.txt'
98     >>> pl.base
99     'bar foo'
100     """
101     def __init__(self, seq = []):
102         UserList.UserList.__init__(self, seq)
103
104     def __getattr__(self, name):
105         # This is how we implement the "special" attributes
106         # such as base, suffix, basepath, etc.
107         try:
108             return self.dictSpecialAttrs[name](self)
109         except KeyError:
110             raise AttributeError, 'PathList has no attribute: %s' % name
111
112     def __splitPath(self, split_func=os.path.split):
113         """This method calls the supplied split_func on each element
114         in the contained list.  We expect split_func to return a
115         2-tuple, usually representing two elements of a split file path,
116         such as those returned by os.path.split().
117
118         We return a 2-tuple of lists, each equal in length to the contained
119         list.  The first list represents all the elements from the
120         first part of the split operation, the second represents
121         all elements from the second part."""
122         list1 = []
123         list2 = []
124         for strPath in self.data:
125             first_part, second_part = split_func(strPath)
126             list1.append(first_part)
127             list2.append(second_part)
128         return (self.__class__(list1),
129                 self.__class__(list2))
130
131     def __getBasePath(self):
132         """Return the file's directory and file name, with the
133         suffix stripped."""
134         return self.__splitPath(os.path.splitext)[0]
135
136     def __getSuffix(self):
137         """Return the file's suffix."""
138         return self.__splitPath(os.path.splitext)[1]
139
140     def __getFileName(self):
141         """Return the file's name without the path."""
142         return self.__splitPath()[1]
143
144     def __getDir(self):
145         """Return the file's path."""
146         return self.__splitPath()[0]
147
148     def __getBase(self):
149         """Return the file name with path and suffix stripped."""
150         return self.__getFileName().__splitPath(os.path.splitext)[0]
151
152     dictSpecialAttrs = { "file" : __getFileName,
153                          "base" : __getBasePath,
154                          "filebase" : __getBase,
155                          "dir" : __getDir,
156                          "suffix" : __getSuffix }
157     
158     def __str__(self):
159         return string.join(self.data)
160
161     def __repr__(self):
162         return repr(string.join(self.data))
163
164     def __getitem__(self, item):
165         # We must do this to ensure that single items returned
166         # by index access have the special attributes such as
167         # suffix and basepath.
168         return self.__class__([ UserList.UserList.__getitem__(self, item), ])
169
170 _cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
171 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
172
173 def scons_subst_list(strSubst, globals, locals, remove=None):
174     """
175     This function is similar to scons_subst(), but with
176     one important difference.  Instead of returning a single
177     string, this function returns a list of lists.
178     The first (outer) list is a list of lines, where the
179     substituted stirng has been broken along newline characters.
180     The inner lists are lists of command line arguments, i.e.,
181     the argv array that should be passed to a spawn or exec
182     function.
183
184     One important thing this guy does is preserve environment
185     variables that are lists.  For instance, if you have
186     an environment variable that is a Python list (or UserList-
187     derived class) that contains path names with spaces in them,
188     then the entire path will be returned as a single argument.
189     This is the only way to know where the 'split' between arguments
190     is for executing a command line."""
191
192     def repl(m, globals=globals, locals=locals):
193         key = m.group(1)
194         if key[:1] == '{' and key[-1:] == '}':
195             key = key[1:-1]
196         try:
197             e = eval(key, globals, locals)
198             if e is None:
199                 s = ''
200             elif is_List(e):
201                 s = string.join(map(str, e), '\0')
202             else:
203                 s = _space_sep.sub('\0', str(e))
204         except NameError:
205             s = ''
206         return s
207     n = 1
208
209     # Tokenize the original string...
210     strSubst = _space_sep.sub('\0', str(strSubst))
211     
212     # Now, do the substitution
213     while n != 0:
214         strSubst, n = _cv.subn(repl, strSubst)
215     # Now parse the whole list into tokens.
216     listLines = string.split(strSubst, '\n')
217     if remove:
218         listLines = map(lambda x,re=remove: re.sub('', x), listLines)
219     return map(lambda x: filter(lambda y: y, string.split(x, '\0')),
220                listLines)
221
222 def scons_subst(strSubst, globals, locals, remove=None):
223     """Recursively interpolates dictionary variables into
224     the specified string, returning the expanded result.
225     Variables are specified by a $ prefix in the string and
226     begin with an initial underscore or alphabetic character
227     followed by any number of underscores or alphanumeric
228     characters.  The construction variable names may be
229     surrounded by curly braces to separate the name from
230     trailing characters.
231     """
232     cmd_list = scons_subst_list(strSubst, globals, locals, remove)
233     return string.join(map(string.join, cmd_list), '\n')
234
235 class VarInterpolator:
236     def __init__(self, dest, src, prefix, suffix):
237         self.dest = dest
238         self.src = src
239         self.prefix = prefix
240         self.suffix = suffix
241
242     def prepareSrc(self, dict):
243         src = dict[self.src]
244         if is_String(src):
245             src = string.split(src)
246         elif not is_List(src):
247             src = [ src ]
248
249         def prepare(x, dict=dict):
250             if isinstance(x, SCons.Node.Node):
251                 return x
252             else:
253                 return scons_subst(x, {}, dict)
254
255         return map(prepare, src)
256
257     def generate(self, dict):
258         if not dict.has_key(self.src):
259             dict[self.dest] = ''
260             return
261
262         src = filter(lambda x: not x is None, self.prepareSrc(dict))
263
264         if not src:
265             dict[self.dest] = ''
266             return
267
268         try:
269             prefix = str(dict[self.prefix])
270         except KeyError:
271             prefix=''
272
273         try:
274             suffix = str(dict[self.suffix])
275         except KeyError:
276             suffix =''
277
278         def autogenFunc(x, suff=suffix, pref=prefix):
279             """Generate the interpolated variable.  If the prefix
280             ends in a space, or the suffix begins in a space,
281             leave it as a separate element of the list."""
282             ret = [ str(x) ]
283             if pref and pref[-1] == ' ':
284                 ret.insert(0, pref[:-1])
285             else:
286                 ret[0] = pref + ret[0]
287             if suff and suff[0] == ' ':
288                 ret.append(suff[1:])
289             else:
290                 ret[-1] = ret[-1] + suff
291             return ret
292         dict[self.dest] = reduce(lambda x, y: x+y,
293                                  map(autogenFunc,
294                                      src))
295
296     def instance(self, dir, fs):
297         return self
298
299 class DirVarInterp(VarInterpolator):
300     def __init__(self, dest, src, prefix, suffix):
301         VarInterpolator.__init__(self, dest, src, prefix, suffix)
302         self.fs = None
303         self.Dir = None
304         self.dictInstCache = {}
305
306     def prepareSrc(self, dict):
307         src = VarInterpolator.prepareSrc(self, dict)
308
309         def prepare(x, self=self):
310             if isinstance(x, SCons.Node.Node):
311                 return x
312             elif str(x):
313                 return self.fs.Dir(str(x), directory=self.dir)
314             else:
315                 return None
316
317         return map(prepare, src)
318
319     def instance(self, dir, fs):
320         try:
321             ret = self.dictInstCache[(dir, fs)]
322         except KeyError:
323             ret = copy.copy(self)
324             ret.fs = fs
325             ret.dir = dir
326             self.dictInstCache[(dir, fs)] = ret
327         return ret
328
329     def generate(self, dict):
330         VarInterpolator.generate(self, dict)
331         if dict[self.dest]:
332             dict[self.dest] = ['$('] + dict[self.dest] + ['$)']
333
334 AUTO_GEN_VARS = ( VarInterpolator('_LIBFLAGS',
335                                   'LIBS',
336                                   'LIBLINKPREFIX',
337                                   'LIBLINKSUFFIX'),
338                   DirVarInterp('_LIBDIRFLAGS',
339                                'LIBPATH',
340                                'LIBDIRPREFIX',
341                                'LIBDIRSUFFIX' ),
342                   DirVarInterp('_INCFLAGS',
343                                'CPPPATH',
344                                'INCPREFIX',
345                                'INCSUFFIX') )
346
347 def autogenerate(dict, fs = SCons.Node.FS.default_fs, dir = None):
348     """Autogenerate the "interpolated" environment variables.
349     We read a static structure that tells us how.  AUTO_GEN_VARS
350     is a tuple of tuples.  Each inner tuple has four elements,
351     each strings referring to an environment variable, and describing
352     how to autogenerate a particular variable.  The elements are:
353
354     0 - The variable to generate
355     1 - The "source" variable, usually a list
356     2 - The "prefix" variable
357     3 - The "suffix" variable
358
359     The autogenerated variable is a list, consisting of every
360     element of the source list, or a single element if the source
361     is a string, with the prefix and suffix
362     concatenated."""
363
364     for interp in AUTO_GEN_VARS:
365         interp.instance(dir, fs).generate(dict)
366
367 def render_tree(root, child_func, margin=[0], visited={}):
368     """
369     Render a tree of nodes into an ASCII tree view.
370     root - the root node of the tree
371     child_func - the function called to get the children of a node
372     margin - the format of the left margin to use for children of root.
373        1 results in a pipe, and 0 results in no pipe.
374     visited - a dictionart of visited nodes in the current branch
375     """
376
377     if visited.has_key(root):
378         return ""
379
380     children = child_func(root)
381     retval = ""
382     for pipe in margin[:-1]:
383         if pipe:
384             retval = retval + "| "
385         else:
386             retval = retval + "  "
387
388     retval = retval + "+-" + str(root) + "\n"
389     visited = copy.copy(visited)
390     visited[root] = 1
391
392     for i in range(len(children)):
393         margin.append(i<len(children)-1)
394         retval = retval + render_tree(children[i], child_func, margin, visited
395 )
396         margin.pop()
397
398     return retval
399
400 def is_Dict(e):
401     return type(e) is types.DictType or isinstance(e, UserDict.UserDict)
402
403 def is_List(e):
404     return type(e) is types.ListType or isinstance(e, UserList.UserList)
405
406 if hasattr(types, 'UnicodeType'):
407     def is_String(e):
408         return type(e) is types.StringType \
409             or type(e) is types.UnicodeType \
410             or isinstance(e, UserString)
411 else:
412     def is_String(e):
413         return type(e) is types.StringType or isinstance(e, UserString)
414
415 # attempt to load the windows registry module:
416 can_read_reg = 0
417 try:
418     import _winreg
419
420     can_read_reg = 1
421     hkey_mod = _winreg
422
423     RegOpenKeyEx = _winreg.OpenKeyEx
424     RegEnumKey = _winreg.EnumKey
425     RegEnumValue = _winreg.EnumValue
426     RegQueryValueEx = _winreg.QueryValueEx
427     RegError = _winreg.error
428
429 except ImportError:
430     try:
431         import win32api
432         import win32con
433         can_read_reg = 1
434         hkey_mod = win32con
435
436         RegOpenKeyEx = win32api.RegOpenKeyEx
437         RegEnumKey = win32api.RegEnumKey
438         RegEnumValue = win32api.RegEnumValue
439         RegQueryValueEx = win32api.RegQueryValueEx
440         RegError = win32api.error
441
442     except ImportError:
443         pass
444
445 if can_read_reg:
446     HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT
447     HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE
448     HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER
449     HKEY_USERS = hkey_mod.HKEY_USERS