Fix stripping the library prefix.
[scons.git] / src / engine / SCons / Util.py
1 """SCons.Util
2
3 Various utility functions go here.
4
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
33 import copy
34 import os
35 import os.path
36 import re
37 import stat
38 import string
39 import sys
40 import types
41 import UserDict
42 import UserList
43 import SCons.Node
44
45 try:
46     from UserString import UserString
47 except ImportError:
48     class UserString:
49         pass
50
51 _altsep = os.altsep
52 if _altsep is None and sys.platform == 'win32':
53     # My ActivePython 2.0.1 doesn't set os.altsep!  What gives?
54     _altsep = '/'
55
56 def splitext(path):
57     "Same as os.path.splitext() but faster."
58     if _altsep:
59         sep = max(string.rfind(path, os.sep), string.rfind(path, _altsep))
60     else:
61         sep = string.rfind(path, os.sep)
62     dot = string.rfind(path, '.')
63     if dot > sep:
64         return path[:dot],path[dot:]
65     else:
66         return path,""
67
68 def updrive(path):
69     """
70     Make the drive letter (if any) upper case.
71     This is useful because Windows is inconsitent on the case
72     of the drive letter, which can cause inconsistencies when
73     calculating command signatures.
74     """
75     drive, rest = os.path.splitdrive(path)
76     if drive:
77         path = string.upper(drive) + rest
78     return path
79
80 if hasattr(types, 'UnicodeType'):
81     def to_String(s):
82         if isinstance(s, UserString):
83             t = type(s.data)
84         else:
85             t = type(s)
86         if t is types.UnicodeType:
87             return unicode(s)
88         else:
89             return str(s)
90 else:
91     to_String = str
92
93 class Literal:
94     """A wrapper for a string.  If you use this object wrapped
95     around a string, then it will be interpreted as literal.
96     When passed to the command interpreter, all special
97     characters will be escaped."""
98     def __init__(self, lstr):
99         self.lstr = lstr
100
101     def __str__(self):
102         return self.lstr
103
104     def is_literal(self):
105         return 1
106
107 class SpecialAttrWrapper(Literal):
108     """This is a wrapper for what we call a 'Node special attribute.'
109     This is any of the attributes of a Node that we can reference from
110     Environment variable substitution, such as $TARGET.abspath or
111     $SOURCES[1].filebase.  We inherit from Literal so we can handle
112     special characters, plus we implement a for_signature method,
113     such that we can return some canonical string during signatutre
114     calculation to avoid unnecessary rebuilds."""
115
116     def __init__(self, lstr, for_signature=None):
117         """The for_signature parameter, if supplied, will be the
118         canonical string we return from for_signature().  Else
119         we will simply return lstr."""
120         Literal.__init__(self, lstr)
121         if for_signature:
122             self.forsig = for_signature
123         else:
124             self.forsig = lstr
125
126     def for_signature(self):
127         return self.forsig
128
129 class CallableComposite(UserList.UserList):
130     """A simple composite callable class that, when called, will invoke all
131     of its contained callables with the same arguments."""
132     def __init__(self, seq = []):
133         UserList.UserList.__init__(self, seq)
134
135     def __call__(self, *args, **kwargs):
136         retvals = map(lambda x, args=args, kwargs=kwargs: apply(x,
137                                                                 args,
138                                                                 kwargs),
139                       self.data)
140         if self.data and (len(self.data) == len(filter(callable, retvals))):
141             return self.__class__(retvals)
142         return NodeList(retvals)
143
144 class NodeList(UserList.UserList):
145     """This class is almost exactly like a regular list of Nodes
146     (actually it can hold any object), with one important difference.
147     If you try to get an attribute from this list, it will return that
148     attribute from every item in the list.  For example:
149
150     >>> someList = NodeList([ '  foo  ', '  bar  ' ])
151     >>> someList.strip()
152     [ 'foo', 'bar' ]
153     """
154     def __init__(self, seq = []):
155         UserList.UserList.__init__(self, seq)
156
157     def __nonzero__(self):
158         return len(self.data) != 0
159
160     def __str__(self):
161         return string.join(map(str, self.data))
162
163     def __getattr__(self, name):
164         if not self.data:
165             # If there is nothing in the list, then we have no attributes to
166             # pass through, so raise AttributeError for everything.
167             raise AttributeError, "NodeList has no attribute: %s" % name
168         
169         # Return a list of the attribute, gotten from every element
170         # in the list
171         attrList = map(lambda x, n=name: getattr(x, n), self.data)
172
173         # Special case.  If the attribute is callable, we do not want
174         # to return a list of callables.  Rather, we want to return a
175         # single callable that, when called, will invoke the function on
176         # all elements of this list.
177         if self.data and (len(self.data) == len(filter(callable, attrList))):
178             return CallableComposite(attrList)
179         return self.__class__(attrList)
180
181     def is_literal(self):
182         return 1
183
184 _valid_var = re.compile(r'[_a-zA-Z]\w*$')
185 _get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
186
187 def is_valid_construction_var(varstr):
188     """Return if the specified string is a legitimate construction
189     variable.
190     """
191     return _valid_var.match(varstr)
192
193 def get_environment_var(varstr):
194     """Given a string, first determine if it looks like a reference
195     to a single environment variable, like "$FOO" or "${FOO}".
196     If so, return that variable with no decorations ("FOO").
197     If not, return None."""
198     mo=_get_env_var.match(to_String(varstr))
199     if mo:
200         var = mo.group(1)
201         if var[0] == '{':
202             return var[1:-1]
203         else:
204             return var
205     else:
206         return None
207
208 def quote_spaces(arg):
209     if ' ' in arg or '\t' in arg:
210         return '"%s"' % arg
211     else:
212         return str(arg)
213
214 # Several functions below deal with Environment variable
215 # substitution.  Part of this process involves inserting
216 # a bunch of special escape sequences into the string
217 # so that when we are all done, we know things like
218 # where to split command line args, what strings to
219 # interpret literally, etc.  A dictionary of these
220 # sequences follows:
221 #
222 # \0\1          signifies a division between arguments in
223 #               a command line.
224 #
225 # \0\2          signifies a division between multiple distinct
226 #               commands, i.e., a newline
227 #
228 # \0\3          This string should be interpreted literally.
229 #               This code occurring anywhere in the string means
230 #               the whole string should have all special characters
231 #               escaped.
232 #
233 # \0\4          A literal dollar sign '$'
234 #
235 # \0\5          Placed before and after interpolated variables
236 #               so that we do not accidentally smush two variables
237 #               together during the recursive interpolation process.
238
239 _cv = re.compile(r'\$([_a-zA-Z][\.\w]*|{[^}]*})')
240 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
241 _newline = re.compile(r'[\r\n]+')
242
243 def _convertArg(x, strconv=to_String):
244     """This function converts an individual argument.  If the
245     argument is to be interpreted literally, with all special
246     characters escaped, then we insert a special code in front
247     of it, so that the command interpreter will know this."""
248     literal = 0
249
250     try:
251         if x.is_literal():
252             literal = 1
253     except AttributeError:
254         pass
255     
256     if not literal:
257         # escape newlines as '\0\2', '\0\1' denotes an argument split
258         # Also escape double-dollar signs to mean the literal dollar sign.
259         return string.replace(_newline.sub('\0\2', strconv(x)), '$$', '\0\4')
260     else:
261         # Interpret non-string args as literals.
262         # The special \0\3 code will tell us to encase this string
263         # in a Literal instance when we are all done
264         # Also escape out any $ signs because we don't want
265         # to continue interpolating a literal.
266         return '\0\3' + string.replace(strconv(x), '$', '\0\4')
267
268 def _convert(x, strconv = to_String):
269     """This function is used to convert construction variable
270     values or the value of strSubst to a string for interpolation.
271     This function follows the rules outlined in the documentaion
272     for scons_subst_list()"""
273     if x is None:
274         return ''
275     elif is_String(x):
276         # escape newlines as '\0\2', '\0\1' denotes an argument split
277         return _convertArg(_space_sep.sub('\0\1', x), strconv)
278     elif is_List(x):
279         # '\0\1' denotes an argument split
280         return string.join(map(lambda x, s=strconv: _convertArg(x, s), x),
281                            '\0\1')
282     else:
283         return _convertArg(x, strconv)
284
285 class CmdStringHolder:
286     """This is a special class used to hold strings generated
287     by scons_subst_list().  It defines a special method escape().
288     When passed a function with an escape algorithm for a
289     particular platform, it will return the contained string
290     with the proper escape sequences inserted."""
291
292     def __init__(self, cmd):
293         """This constructor receives a string.  The string
294         can contain the escape sequence \0\3.
295         If it does, then we will escape all special characters
296         in the string before passing it to the command interpreter."""
297         self.data = cmd
298         
299         # Populate flatdata (the thing returned by str()) with the
300         # non-escaped string
301         self.escape(lambda x: x, lambda x: x)
302
303     def __str__(self):
304         """Return the string in its current state."""
305         return self.flatdata
306
307     def __len__(self):
308         """Return the length of the string in its current state."""
309         return len(self.flatdata)
310
311     def __getitem__(self, index):
312         """Return the index'th element of the string in its current state."""
313         return self.flatdata[index]
314
315     def escape(self, escape_func, quote_func=quote_spaces):
316         """Escape the string with the supplied function.  The
317         function is expected to take an arbitrary string, then
318         return it with all special characters escaped and ready
319         for passing to the command interpreter.
320
321         After calling this function, the next call to str() will
322         return the escaped string.
323         """
324
325         if string.find(self.data, '\0\3') >= 0:
326             self.flatdata = escape_func(string.replace(self.data, '\0\3', ''))
327         elif ' ' in self.data or '\t' in self.data:
328             self.flatdata = quote_func(self.data)
329         else:
330             self.flatdata = self.data
331
332     def __cmp__(self, rhs):
333         return cmp(self.flatdata, str(rhs))
334         
335 class DisplayEngine:
336     def __init__(self):
337         self.__call__ = self.print_it
338
339     def print_it(self, text):
340         sys.stdout.write(text + '\n')
341
342     def dont_print(self, text):
343         pass
344
345     def set_mode(self, mode):
346         if mode:
347             self.__call__ = self.print_it
348         else:
349             self.__call__ = self.dont_print
350
351 def target_prep(target):
352     if target and not isinstance(target, NodeList):
353         if not is_List(target):
354             target = [target]
355         target = NodeList(map(lambda x: x.get_subst_proxy(), target))
356     return target
357
358 def source_prep(source):
359     if source and not isinstance(source, NodeList):
360         if not is_List(source):
361             source = [source]
362         source = NodeList(map(lambda x: x.rfile().get_subst_proxy(), source))
363     return source
364
365 def subst_dict(target, source, env):
366     """Create a dictionary for substitution of special
367     construction variables.
368
369     This translates the following special arguments:
370
371     target - the target (object or array of objects),
372              used to generate the TARGET and TARGETS
373              construction variables
374
375     source - the source (object or array of objects),
376              used to generate the SOURCES and SOURCE
377              construction variables
378
379     env    - the construction Environment used for this
380              build, which is made available as the __env__
381              construction variable
382     """
383     dict = { '__env__' : env }
384
385     target = target_prep(target)
386     dict['TARGETS'] = target
387     if dict['TARGETS']:
388         dict['TARGET'] = dict['TARGETS'][0]
389
390     source = source_prep(source)
391     dict['SOURCES'] = source
392     if dict['SOURCES']:
393         dict['SOURCE'] = dict['SOURCES'][0]
394
395     return dict
396
397 # Constants for the "mode" parameter to scons_subst_list() and
398 # scons_subst().  SUBST_RAW gives the raw command line.  SUBST_CMD
399 # gives a command line suitable for passing to a shell.  SUBST_SIG
400 # gives a command line appropriate for calculating the signature
401 # of a command line...if this changes, we should rebuild.
402 SUBST_RAW = 0
403 SUBST_CMD = 1
404 SUBST_SIG = 2
405
406 _rm = re.compile(r'\$[()]')
407 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
408
409 def _canonicalize(obj):
410     """Attempt to call the object's for_signature method,
411     which is expected to return a string suitable for use in calculating
412     a command line signature (i.e., it only changes when we should
413     rebuild the target).  For instance, file Nodes will report only
414     their file name (with no path), so changing Repository settings
415     will not cause a rebuild."""
416     try:
417         return obj.for_signature()
418     except AttributeError:
419         return to_String(obj)
420
421 # Indexed by the SUBST_* constants above.
422 _regex_remove = [ None, _rm, _remove ]
423 _strconv = [ to_String, to_String, _canonicalize ]
424
425 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None):
426     """
427     This function serves the same purpose as scons_subst(), except
428     this function returns the interpolated list as a list of lines, where
429     each line is a list of command line arguments. In other words:
430     The first (outer) list is a list of lines, where the
431     substituted stirng has been broken along newline characters.
432     The inner lists are lists of command line arguments, i.e.,
433     the argv array that should be passed to a spawn or exec
434     function.
435
436     There are a few simple rules this function follows in order to
437     determine how to parse strSubst and construction variables into lines
438     and arguments:
439
440     1) A string is interpreted as a space delimited list of arguments.
441     2) A list is interpreted as a list of arguments. This allows arguments
442        with spaces in them to be expressed easily.
443     4) Anything that is not a list or string (e.g. a Node instance) is
444        interpreted as a single argument, and is converted to a string.
445     3) Newline (\n) characters delimit lines. The newline parsing is done
446        after all the other parsing, so it is not possible for arguments
447        (e.g. file names) to contain embedded newline characters.
448     """
449
450     remove = _regex_remove[mode]
451     strconv = _strconv[mode]
452
453     def repl(m,
454              target=target,
455              source=source,
456              env=env,
457              local_vars = subst_dict(target, source, env),
458              global_vars = env.Dictionary(),
459              strconv=strconv,
460              sig=(mode != SUBST_CMD)):
461         key = m.group(1)
462         if key[0] == '{':
463             key = key[1:-1]
464         try:
465             e = eval(key, global_vars, local_vars)
466         except (IndexError, NameError, TypeError):
467             return '\0\5'
468         if callable(e):
469             # We wait to evaluate callables until the end of everything
470             # else.  For now, we instert a special escape sequence
471             # that we will look for later.
472             return '\0\5' + _convert(e(target=target,
473                                        source=source,
474                                        env=env,
475                                        for_signature=sig),
476                                      strconv) + '\0\5'
477         else:
478             # The \0\5 escape code keeps us from smushing two or more
479             # variables together during recusrive substitution, i.e.
480             # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
481             return "\0\5" + _convert(e, strconv) + "\0\5"
482
483     # Convert the argument to a string:
484     strSubst = _convert(strSubst, strconv)
485
486     # Do the interpolation:
487     n = 1
488     while n != 0:
489         strSubst, n = _cv.subn(repl, strSubst)
490         
491     # Convert the interpolated string to a list of lines:
492     listLines = string.split(strSubst, '\0\2')
493
494     # Remove the patterns that match the remove argument: 
495     if remove:
496         listLines = map(lambda x,re=remove: re.sub('', x), listLines)
497
498     # Process escaped $'s and remove placeholder \0\5's
499     listLines = map(lambda x: string.replace(string.replace(x, '\0\4', '$'), '\0\5', ''), listLines)
500
501     # Finally split each line up into a list of arguments:
502     return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))),
503                listLines)
504
505 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None):
506     """Recursively interpolates dictionary variables into
507     the specified string, returning the expanded result.
508     Variables are specified by a $ prefix in the string and
509     begin with an initial underscore or alphabetic character
510     followed by any number of underscores or alphanumeric
511     characters.  The construction variable names may be
512     surrounded by curly braces to separate the name from
513     trailing characters.
514     """
515
516     # This function needs to be fast, so don't call scons_subst_list
517
518     remove = _regex_remove[mode]
519     strconv = _strconv[mode]
520
521     def repl(m,
522              target=target,
523              source=source,
524              env=env,
525              local_vars = subst_dict(target, source, env),
526              global_vars = env.Dictionary(),
527              strconv=strconv,
528              sig=(mode != SUBST_CMD)):
529         key = m.group(1)
530         if key[0] == '{':
531             key = key[1:-1]
532         try:
533             e = eval(key, global_vars, local_vars)
534         except (IndexError, NameError, TypeError):
535             return '\0\5'
536         if callable(e):
537             e = e(target=target, source=source, env=env,
538                   for_signature = sig)
539
540         def conv(arg, strconv=strconv):
541             literal = 0
542             try:
543                 if arg.is_literal():
544                     literal = 1
545             except AttributeError:
546                 pass
547             ret = strconv(arg)
548             if literal:
549                 # Escape dollar signs to prevent further
550                 # substitution on literals.
551                 ret = string.replace(ret, '$', '\0\4')
552             return ret
553         if e is None:
554             s = ''
555         elif is_List(e):
556             s = string.join(map(conv, e), ' ')
557         else:
558             s = conv(e)
559         # Insert placeholders to avoid accidentally smushing
560         # separate variables together.
561         return "\0\5" + s + "\0\5"
562
563     # Now, do the substitution
564     n = 1
565     while n != 0:
566         # escape double dollar signs
567         strSubst = string.replace(strSubst, '$$', '\0\4')
568         strSubst,n = _cv.subn(repl, strSubst)
569
570     # remove the remove regex
571     if remove:
572         strSubst = remove.sub('', strSubst)
573
574     # Un-escape the string
575     strSubst = string.replace(string.replace(strSubst, '\0\4', '$'),
576                               '\0\5', '')
577     # strip out redundant white-space
578     if mode != SUBST_RAW:
579         strSubst = string.strip(_space_sep.sub(' ', strSubst))
580     return strSubst
581
582 def render_tree(root, child_func, prune=0, margin=[0], visited={}):
583     """
584     Render a tree of nodes into an ASCII tree view.
585     root - the root node of the tree
586     child_func - the function called to get the children of a node
587     prune - don't visit the same node twice
588     margin - the format of the left margin to use for children of root.
589        1 results in a pipe, and 0 results in no pipe.
590     visited - a dictionary of visited nodes in the current branch if not prune,
591        or in the whole tree if prune.
592     """
593
594     if visited.has_key(root):
595         return ""
596
597     children = child_func(root)
598     retval = ""
599     for pipe in margin[:-1]:
600         if pipe:
601             retval = retval + "| "
602         else:
603             retval = retval + "  "
604
605     retval = retval + "+-" + str(root) + "\n"
606     if not prune:
607         visited = copy.copy(visited)
608     visited[root] = 1
609
610     for i in range(len(children)):
611         margin.append(i<len(children)-1)
612         retval = retval + render_tree(children[i], child_func, prune, margin, visited
613 )
614         margin.pop()
615
616     return retval
617
618 def is_Dict(e):
619     return type(e) is types.DictType or isinstance(e, UserDict.UserDict)
620
621 def is_List(e):
622     return type(e) is types.ListType or isinstance(e, UserList.UserList)
623
624 def mapPaths(paths, dir, env=None):
625     """Takes a single node or string, or a list of nodes and/or
626     strings.  We leave the nodes untouched, but we put the strings
627     under the supplied directory node dir, if they are not an absolute
628     path.
629
630     For instance, the following:
631
632     n = SCons.Node.FS.default_fs.File('foo')
633     mapPaths([ n, 'foo', '/bar' ],
634              SCons.Node.FS.default_fs.Dir('baz'), env)
635
636     ...would return:
637
638     [ n, 'baz/foo', '/bar' ]
639
640     The env argument, if given, is used to perform variable
641     substitution on the source string(s).
642     """
643
644     def mapPathFunc(path, dir=dir, env=env):
645         if is_String(path):
646             if env:
647                 path = env.subst(path)
648             if dir:
649                 if not path:
650                     return str(dir)
651                 if os.path.isabs(path) or path[0] == '#':
652                     return path
653                 return str(dir) + os.sep + path
654         return path
655
656     if not is_List(paths):
657         paths = [ paths ]
658     ret = map(mapPathFunc, paths)
659     return ret
660     
661
662 if hasattr(types, 'UnicodeType'):
663     def is_String(e):
664         return type(e) is types.StringType \
665             or type(e) is types.UnicodeType \
666             or isinstance(e, UserString)
667 else:
668     def is_String(e):
669         return type(e) is types.StringType or isinstance(e, UserString)
670
671 class Proxy:
672     """A simple generic Proxy class, forwarding all calls to
673     subject.  Inherit from this class to create a Proxy."""
674     def __init__(self, subject):
675         self.__subject = subject
676         
677     def __getattr__(self, name):
678         return getattr(self.__subject, name)
679
680     def get(self):
681         return self.__subject
682
683 # attempt to load the windows registry module:
684 can_read_reg = 0
685 try:
686     import _winreg
687
688     can_read_reg = 1
689     hkey_mod = _winreg
690
691     RegOpenKeyEx = _winreg.OpenKeyEx
692     RegEnumKey = _winreg.EnumKey
693     RegEnumValue = _winreg.EnumValue
694     RegQueryValueEx = _winreg.QueryValueEx
695     RegError = _winreg.error
696
697 except ImportError:
698     try:
699         import win32api
700         import win32con
701         can_read_reg = 1
702         hkey_mod = win32con
703
704         RegOpenKeyEx = win32api.RegOpenKeyEx
705         RegEnumKey = win32api.RegEnumKey
706         RegEnumValue = win32api.RegEnumValue
707         RegQueryValueEx = win32api.RegQueryValueEx
708         RegError = win32api.error
709
710     except ImportError:
711         class _NoError(Exception):
712             pass
713         RegError = _NoError
714
715 if can_read_reg:
716     HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT
717     HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE
718     HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER
719     HKEY_USERS = hkey_mod.HKEY_USERS
720
721     def RegGetValue(root, key):
722         """This utility function returns a value in the registry
723         without having to open the key first.  Only available on
724         Windows platforms with a version of Python that can read the
725         registry.  Returns the same thing as
726         SCons.Util.RegQueryValueEx, except you just specify the entire
727         path to the value, and don't have to bother opening the key
728         first.  So:
729
730         Instead of:
731           k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
732                 r'SOFTWARE\Microsoft\Windows\CurrentVersion')
733           out = SCons.Util.RegQueryValueEx(k,
734                 'ProgramFilesDir')
735
736         You can write:
737           out = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
738                 r'SOFTWARE\Microsoft\Windows\CurrentVersion\ProgramFilesDir')
739         """
740         # I would use os.path.split here, but it's not a filesystem
741         # path...
742         p = key.rfind('\\') + 1
743         keyp = key[:p]
744         val = key[p:]
745         k = SCons.Util.RegOpenKeyEx(root, keyp)
746         return SCons.Util.RegQueryValueEx(k,val)
747
748 if sys.platform == 'win32':
749
750     def WhereIs(file, path=None, pathext=None):
751         if path is None:
752             path = os.environ['PATH']
753         if is_String(path):
754             path = string.split(path, os.pathsep)
755         if pathext is None:
756             try:
757                 pathext = os.environ['PATHEXT']
758             except KeyError:
759                 pathext = '.COM;.EXE;.BAT;.CMD'
760         if is_String(pathext):
761             pathext = string.split(pathext, os.pathsep)
762         for ext in pathext:
763             if string.lower(ext) == string.lower(file[-len(ext):]):
764                 pathext = ['']
765                 break
766         for dir in path:
767             f = os.path.join(dir, file)
768             for ext in pathext:
769                 fext = f + ext
770                 if os.path.isfile(fext):
771                     return os.path.normpath(fext)
772         return None
773
774 elif os.name == 'os2':
775
776     def WhereIs(file, path=None, pathext=None):
777         if path is None:
778             path = os.environ['PATH']
779         if is_String(path):
780             path = string.split(path, os.pathsep)
781         if pathext is None:
782             pathext = ['.exe', '.cmd']
783         for ext in pathext:
784             if string.lower(ext) == string.lower(file[-len(ext):]):
785                 pathext = ['']
786                 break
787         for dir in path:
788             f = os.path.join(dir, file)
789             for ext in pathext:
790                 fext = f + ext
791                 if os.path.isfile(fext):
792                     return os.path.normpath(fext)
793         return None
794
795 else:
796
797     def WhereIs(file, path=None, pathext=None):
798         if path is None:
799             path = os.environ['PATH']
800         if is_String(path):
801             path = string.split(path, os.pathsep)
802         for dir in path:
803             f = os.path.join(dir, file)
804             if os.path.isfile(f):
805                 try:
806                     st = os.stat(f)
807                 except OSError:
808                     continue
809                 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
810                     return os.path.normpath(f)
811         return None
812
813 def PrependPath(oldpath, newpath, sep = os.pathsep):
814     """This prepends newpath elements to the given oldpath.  Will only
815     add any particular path once (leaving the first one it encounters
816     and ignoring the rest, to preserve path order), and will
817     os.path.normpath and os.path.normcase all paths to help assure
818     this.  This can also handle the case where the given old path
819     variable is a list instead of a string, in which case a list will
820     be returned instead of a string.
821
822     Example:
823       Old Path: "/foo/bar:/foo"
824       New Path: "/biz/boom:/foo"
825       Result:   "/biz/boom:/foo:/foo/bar"
826     """
827
828     orig = oldpath
829     is_list = 1
830     paths = orig
831     if not SCons.Util.is_List(orig):
832         paths = string.split(paths, sep)
833         is_list = 0
834
835     if SCons.Util.is_List(newpath):
836         newpaths = newpath
837     else:
838         newpaths = string.split(newpath, sep)
839
840     newpaths = newpaths + paths # prepend new paths
841
842     normpaths = []
843     paths = []
844     # now we add them only of they are unique
845     for path in newpaths:
846         normpath = os.path.normpath(os.path.normcase(path))
847         if path and not normpath in normpaths:
848             paths.append(path)
849             normpaths.append(normpath)
850
851     if is_list:
852         return paths
853     else:
854         return string.join(paths, sep)
855
856 def AppendPath(oldpath, newpath, sep = os.pathsep):
857     """This appends new path elements to the given old path.  Will
858     only add any particular path once (leaving the last one it
859     encounters and ignoring the rest, to preserve path order), and
860     will os.path.normpath and os.path.normcase all paths to help
861     assure this.  This can also handle the case where the given old
862     path variable is a list instead of a string, in which case a list
863     will be returned instead of a string.
864
865     Example:
866       Old Path: "/foo/bar:/foo"
867       New Path: "/biz/boom:/foo"
868       Result:   "/foo/bar:/biz/boom:/foo"
869     """
870
871     orig = oldpath
872     is_list = 1
873     paths = orig
874     if not SCons.Util.is_List(orig):
875         paths = string.split(paths, sep)
876         is_list = 0
877
878     if SCons.Util.is_List(newpath):
879         newpaths = newpath
880     else:
881         newpaths = string.split(newpath, sep)
882
883     newpaths = paths + newpaths # append new paths
884     newpaths.reverse()
885     
886     normpaths = []
887     paths = []
888     # now we add them only of they are unique
889     for path in newpaths:
890         normpath = os.path.normpath(os.path.normcase(path))
891         if path and not normpath in normpaths:
892             paths.append(path)
893             normpaths.append(normpath)
894
895     paths.reverse()
896
897     if is_list:
898         return paths
899     else:
900         return string.join(paths, sep)
901
902
903 def dir_index(directory):
904     files = []
905     for file in os.listdir(directory):
906         fullname = os.path.join(directory, file)
907         files.append(fullname)
908
909     # os.listdir() isn't guaranteed to return files in any specific order,
910     # but some of the test code expects sorted output.
911     files.sort()
912     return files
913
914 def fs_delete(path, remove=1):
915     try:
916         if os.path.exists(path):
917             if os.path.isfile(path):
918                 if remove: os.unlink(path)
919                 display("Removed " + path)
920             elif os.path.isdir(path) and not os.path.islink(path):
921                 # delete everything in the dir
922                 for p in dir_index(path):
923                     if os.path.isfile(p):
924                         if remove: os.unlink(p)
925                         display("Removed " + p)
926                     else:
927                         fs_delete(p, remove)
928                 # then delete dir itself
929                 if remove: os.rmdir(path)
930                 display("Removed directory " + path)
931     except OSError, e:
932         print "scons: Could not remove '%s':" % str(path), e.strerror
933
934 if sys.platform == 'cygwin':
935     def get_native_path(path):
936         """Transforms an absolute path into a native path for the system.  In
937         Cygwin, this converts from a Cygwin path to a Win32 one."""
938         return string.replace(os.popen('cygpath -w ' + path).read(), '\n', '')
939 else:
940     def get_native_path(path):
941         """Transforms an absolute path into a native path for the system.
942         Non-Cygwin version, just leave the path alone."""
943         return path
944
945 display = DisplayEngine()
946
947 class Selector(UserDict.UserDict):
948     """A callable dictionary that maps file suffixes to dictionary
949     values."""
950     def __call__(self, env, source):
951         ext = splitext(str(source[0]))[1]
952         try:
953             return self[ext]
954         except KeyError:
955             # Try to perform Environment substitution on the keys of
956             # emitter_dict before giving up.
957             s_dict = {}
958             for (k,v) in self.items():
959                 if not k is None:
960                     s_k = env.subst(k)
961                     if s_dict.has_key(s_k):
962                         # We only raise an error when variables point
963                         # to the same suffix.  If one suffix is literal
964                         # and a variable suffix contains this literal,
965                         # the literal wins and we don't raise an error.
966                         raise KeyError, (s_dict[s_k][0], k, s_k)
967                     s_dict[s_k] = (k,v)
968             try:
969                 return s_dict[ext][1]
970             except KeyError:
971                 try:
972                     return self[None]
973                 except KeyError:
974                     return None
975
976
977 if sys.platform == 'cygwin':
978     # On Cygwin, os.path.normcase() lies, so just report back the
979     # fact that the underlying Win32 OS is case-insensitive.
980     def case_sensitive_suffixes(s1, s2):
981         return 0
982 else:
983     def case_sensitive_suffixes(s1, s2):
984         return (os.path.normcase(s1) != os.path.normcase(s2))
985
986 def adjustixes(file, pre, suf):
987     if pre:
988         path, fn = os.path.split(os.path.normpath(file))
989         if fn[:len(pre)] != pre:
990             file = os.path.join(path, pre + fn)
991     # Only append a suffix if the file does not have one.
992     if suf and not splitext(file)[1] and file[-len(suf):] != suf:
993             file = file + suf
994     return file