3 Various utility functions go here.
8 # Copyright (c) 2001, 2002 Steven Knight
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
45 from UserString import UserString
51 if _altsep is None and sys.platform == 'win32':
52 # My ActivePython 2.0.1 doesn't set os.altsep! What gives?
56 "Same as os.path.splitext() but faster."
58 sep = max(string.rfind(path, os.sep), string.rfind(path, _altsep))
60 sep = string.rfind(path, os.sep)
61 dot = string.rfind(path, '.')
63 return path[:dot],path[dot:]
69 Make the drive letter (if any) upper case.
70 This is useful because Windows is inconsitent on the case
71 of the drive letter, which can cause inconsistencies when
72 calculating command signatures.
74 drive, rest = os.path.splitdrive(path)
76 path = string.upper(drive) + rest
80 """A wrapper for a string. If you use this object wrapped
81 around a string, then it will be interpreted as literal.
82 When passed to the command interpreter, all special
83 characters will be escaped."""
84 def __init__(self, lstr):
93 class PathList(UserList.UserList):
94 """This class emulates the behavior of a list, but also implements
95 the special "path dissection" attributes we can use to find
96 suffixes, base names, etc. of the paths in the list.
98 One other special attribute of this class is that, by
99 overriding the __str__ and __repr__ methods, this class
100 represents itself as a space-concatenated string of
101 the list elements, as in:
103 >>> pl=PathList(["/foo/bar.txt", "/baz/foo.txt"])
105 '/foo/bar.txt /baz/foo.txt'
109 def __init__(self, seq = []):
110 UserList.UserList.__init__(self, seq)
112 def __getattr__(self, name):
113 # This is how we implement the "special" attributes
114 # such as base, suffix, basepath, etc.
116 return self.dictSpecialAttrs[name](self)
118 raise AttributeError, 'PathList has no attribute: %s' % name
120 def __splitPath(self, split_func=os.path.split):
121 """This method calls the supplied split_func on each element
122 in the contained list. We expect split_func to return a
123 2-tuple, usually representing two elements of a split file path,
124 such as those returned by os.path.split().
126 We return a 2-tuple of lists, each equal in length to the contained
127 list. The first list represents all the elements from the
128 first part of the split operation, the second represents
129 all elements from the second part."""
132 for strPath in self.data:
133 first_part, second_part = split_func(strPath)
134 list1.append(first_part)
135 list2.append(second_part)
136 return (self.__class__(list1),
137 self.__class__(list2))
139 def __getBasePath(self):
140 """Return the file's directory and file name, with the
142 return self.__splitPath(splitext)[0]
144 def __getSuffix(self):
145 """Return the file's suffix."""
146 return self.__splitPath(splitext)[1]
148 def __getFileName(self):
149 """Return the file's name without the path."""
150 return self.__splitPath()[1]
153 """Return the file's path."""
154 return self.__splitPath()[0]
157 """Return the file name with path and suffix stripped."""
158 return self.__getFileName().__splitPath(splitext)[0]
160 def __getAbsPath(self):
161 """Return the absolute path"""
162 return map(lambda x: updrive(os.path.abspath(x)), self.data)
164 dictSpecialAttrs = { "file" : __getFileName,
165 "base" : __getBasePath,
166 "filebase" : __getBase,
168 "suffix" : __getSuffix,
169 "abspath" : __getAbsPath}
171 def is_literal(self):
175 return string.join(self.data)
178 return repr(string.join(self.data))
180 def __getitem__(self, item):
181 # We must do this to ensure that single items returned
182 # by index access have the special attributes such as
183 # suffix and basepath.
184 return self.__class__([ UserList.UserList.__getitem__(self, item), ])
186 _env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[^}]*})$')
188 def get_environment_var(varstr):
189 """Given a string, first determine if it looks like a reference
190 to a single environment variable, like "$FOO" or "${FOO}".
191 If so, return that variable with no decorations ("FOO").
192 If not, return None."""
193 mo=_env_var.match(to_String(varstr))
203 def quote_spaces(arg):
204 if ' ' in arg or '\t' in arg:
209 # Several functions below deal with Environment variable
210 # substitution. Part of this process involves inserting
211 # a bunch of special escape sequences into the string
212 # so that when we are all done, we know things like
213 # where to split command line args, what strings to
214 # interpret literally, etc. A dictionary of these
217 # \0\1 signifies a division between arguments in
220 # \0\2 signifies a division between multiple distinct
223 # \0\3 This string should be interpreted literally.
224 # This code occurring anywhere in the string means
225 # the whole string should have all special characters
228 # \0\4 A literal dollar sign '$'
230 # \0\5 Placed before and after interpolated variables
231 # so that we do not accidentally smush to variables
232 # together during the recursive interpolation process.
234 _cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
235 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
236 _newline = re.compile(r'[\r\n]+')
239 """This function converts an individual argument. If the
240 argument is to be interpreted literally, with all special
241 characters escaped, then we insert a special code in front
242 of it, so that the command interpreter will know this."""
248 except AttributeError:
252 # escape newlines as '\0\2', '\0\1' denotes an argument split
253 # Also escape double-dollar signs to mean the literal dollar sign.
254 return string.replace(_newline.sub('\0\2', to_String(x)), '$$', '\0\4')
256 # Interpret non-string args as literals.
257 # The special \0\3 code will tell us to encase this string
258 # in a Literal instance when we are all done
259 # Also escape out any $ signs because we don't want
260 # to continue interpolating a literal.
261 return '\0\3' + string.replace(str(x), '$', '\0\4')
264 """This function is used to convert construction variable
265 values or the value of strSubst to a string for interpolation.
266 This function follows the rules outlined in the documentaion
267 for scons_subst_list()"""
271 # escape newlines as '\0\2', '\0\1' denotes an argument split
272 return _convertArg(_space_sep.sub('\0\1', x))
274 # '\0\1' denotes an argument split
275 return string.join(map(_convertArg, x), '\0\1')
277 return _convertArg(x)
279 class CmdStringHolder:
280 """This is a special class used to hold strings generated
281 by scons_subst_list(). It defines a special method escape().
282 When passed a function with an escape algorithm for a
283 particular platform, it will return the contained string
284 with the proper escape sequences inserted."""
286 def __init__(self, cmd):
287 """This constructor receives a string. The string
288 can contain the escape sequence \0\3.
289 If it does, then we will escape all special characters
290 in the string before passing it to the command interpreter."""
293 # Populate flatdata (the thing returned by str()) with the
295 self.escape(lambda x: x, lambda x: x)
298 """Return the string in its current state."""
302 """Return the length of the string in its current state."""
303 return len(self.flatdata)
305 def __getitem__(self, index):
306 """Return the index'th element of the string in its current state."""
307 return self.flatdata[index]
309 def escape(self, escape_func, quote_func=quote_spaces):
310 """Escape the string with the supplied function. The
311 function is expected to take an arbitrary string, then
312 return it with all special characters escaped and ready
313 for passing to the command interpreter.
315 After calling this function, the next call to str() will
316 return the escaped string.
319 if string.find(self.data, '\0\3') >= 0:
320 self.flatdata = escape_func(string.replace(self.data, '\0\3', ''))
321 elif ' ' in self.data or '\t' in self.data:
322 self.flatdata = quote_func(self.data)
324 self.flatdata = self.data
326 def __cmp__(self, rhs):
327 return cmp(self.flatdata, str(rhs))
331 self.__call__ = self.print_it
333 def print_it(self, text):
336 def dont_print(self, text):
339 def set_mode(self, mode):
341 self.__call__ = self.print_it
343 self.__call__ = self.dont_print
346 def scons_subst_list(strSubst, globals, locals, remove=None):
348 This function serves the same purpose as scons_subst(), except
349 this function returns the interpolated list as a list of lines, where
350 each line is a list of command line arguments. In other words:
351 The first (outer) list is a list of lines, where the
352 substituted stirng has been broken along newline characters.
353 The inner lists are lists of command line arguments, i.e.,
354 the argv array that should be passed to a spawn or exec
357 There are a few simple rules this function follows in order to
358 determine how to parse strSubst and consruction variables into lines
361 1) A string is interpreted as a space delimited list of arguments.
362 2) A list is interpreted as a list of arguments. This allows arguments
363 with spaces in them to be expressed easily.
364 4) Anything that is not a list or string (e.g. a Node instance) is
365 interpreted as a single argument, and is converted to a string.
366 3) Newline (\n) characters delimit lines. The newline parsing is done
367 after all the other parsing, so it is not possible for arguments
368 (e.g. file names) to contain embedded newline characters.
372 """This function is used to convert construction variable
373 values or the value of strSubst to a string for interpolation.
374 This function follows the rules outlined in the documentaion
375 for scons_subst_list()"""
379 return _space_sep.sub('\0', x)
381 return string.join(map(to_String, x), '\0')
385 def repl(m, globals=globals, locals=locals):
390 e = eval(key, globals, locals)
391 # The \0\5 escape code keeps us from smushing two or more
392 # variables together during recusrive substitution, i.e.
393 # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
394 return "\0\5" + _convert(e) + "\0\5"
398 # Convert the argument to a string:
399 strSubst = _convert(strSubst)
401 # Do the interpolation:
404 strSubst, n = _cv.subn(repl, strSubst)
406 # Convert the interpolated string to a list of lines:
407 listLines = string.split(strSubst, '\0\2')
409 # Remove the patterns that match the remove argument:
411 listLines = map(lambda x,re=remove: re.sub('', x), listLines)
413 # Process escaped $'s and remove placeholder \0\5's
414 listLines = map(lambda x: string.replace(string.replace(x, '\0\4', '$'), '\0\5', ''), listLines)
416 # Finally split each line up into a list of arguments:
417 return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))),
420 def scons_subst(strSubst, globals, locals, remove=None):
421 """Recursively interpolates dictionary variables into
422 the specified string, returning the expanded result.
423 Variables are specified by a $ prefix in the string and
424 begin with an initial underscore or alphabetic character
425 followed by any number of underscores or alphanumeric
426 characters. The construction variable names may be
427 surrounded by curly braces to separate the name from
431 # This function needs to be fast, so don't call scons_subst_list
433 def repl(m, globals=globals, locals=locals):
438 e = eval(key, globals, locals)
442 s = string.join(map(to_String, e), ' ')
447 # Insert placeholders to avoid accidentally smushing
448 # separate variables together.
449 return "\0\5" + s + "\0\5"
451 # Now, do the substitution
454 # escape double dollar signs
455 strSubst = string.replace(strSubst, '$$', '\0\4')
456 strSubst,n = _cv.subn(repl, strSubst)
457 # and then remove remove
459 strSubst = remove.sub('', strSubst)
461 # Un-escape the string
462 strSubst = string.replace(string.replace(strSubst, '\0\4', '$'),
464 # strip out redundant white-space
465 return string.strip(_space_sep.sub(' ', strSubst))
467 def render_tree(root, child_func, margin=[0], visited={}):
469 Render a tree of nodes into an ASCII tree view.
470 root - the root node of the tree
471 child_func - the function called to get the children of a node
472 margin - the format of the left margin to use for children of root.
473 1 results in a pipe, and 0 results in no pipe.
474 visited - a dictionart of visited nodes in the current branch
477 if visited.has_key(root):
480 children = child_func(root)
482 for pipe in margin[:-1]:
484 retval = retval + "| "
486 retval = retval + " "
488 retval = retval + "+-" + str(root) + "\n"
489 visited = copy.copy(visited)
492 for i in range(len(children)):
493 margin.append(i<len(children)-1)
494 retval = retval + render_tree(children[i], child_func, margin, visited
501 return type(e) is types.DictType or isinstance(e, UserDict.UserDict)
504 return type(e) is types.ListType or isinstance(e, UserList.UserList)
507 """Better than str() because it will preserve a unicode
508 object without converting it to ASCII."""
509 if hasattr(types, 'UnicodeType') and \
510 (type(s) is types.UnicodeType or \
511 (isinstance(s, UserString) and type(s.data) is types.UnicodeType)):
520 """This function converts a string or list into a list of strings
521 or Nodes. This makes things easier for users by allowing files to
522 be specified as a white-space separated list to be split.
524 - A single string containing names separated by spaces. These will be
525 split apart at the spaces.
526 - A single Node instance
527 - A list containing either strings or Node instances. Any strings
528 in the list are not split at spaces.
529 In all cases, the function returns a list of Nodes and strings."""
533 return string.split(arg)
537 def mapPaths(paths, dir, env=None):
538 """Takes a single node or string, or a list of nodes and/or
539 strings. We leave the nodes untouched, but we put the strings
540 under the supplied directory node dir, if they are not an absolute
543 For instance, the following:
545 n = SCons.Node.FS.default_fs.File('foo')
546 mapPaths([ n, 'foo', '/bar' ],
547 SCons.Node.FS.default_fs.Dir('baz'), env)
551 [ n, 'baz/foo', '/bar' ]
553 The env argument, if given, is used to perform variable
554 substitution on the source string(s).
557 def mapPathFunc(path, dir=dir, env=env):
560 path = env.subst(path)
564 if os.path.isabs(path) or path[0] == '#':
566 return dir.path_ + path
569 if not is_List(paths):
571 ret = map(mapPathFunc, paths)
575 if hasattr(types, 'UnicodeType'):
577 return type(e) is types.StringType \
578 or type(e) is types.UnicodeType \
579 or isinstance(e, UserString)
582 return type(e) is types.StringType or isinstance(e, UserString)
585 """A simple generic Proxy class, forwarding all calls to
586 subject. Inherit from this class to create a Proxy."""
587 def __init__(self, subject):
588 self.__subject = subject
590 def __getattr__(self, name):
591 return getattr(self.__subject, name)
593 # attempt to load the windows registry module:
601 RegOpenKeyEx = _winreg.OpenKeyEx
602 RegEnumKey = _winreg.EnumKey
603 RegEnumValue = _winreg.EnumValue
604 RegQueryValueEx = _winreg.QueryValueEx
605 RegError = _winreg.error
614 RegOpenKeyEx = win32api.RegOpenKeyEx
615 RegEnumKey = win32api.RegEnumKey
616 RegEnumValue = win32api.RegEnumValue
617 RegQueryValueEx = win32api.RegQueryValueEx
618 RegError = win32api.error
624 HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT
625 HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE
626 HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER
627 HKEY_USERS = hkey_mod.HKEY_USERS
630 if sys.platform == 'win32':
632 def WhereIs(file, path=None, pathext=None):
634 path = os.environ['PATH']
636 path = string.split(path, os.pathsep)
639 pathext = os.environ['PATHEXT']
641 pathext = '.COM;.EXE;.BAT;.CMD'
642 if is_String(pathext):
643 pathext = string.split(pathext, os.pathsep)
645 if string.lower(ext) == string.lower(file[-len(ext):]):
649 f = os.path.join(dir, file)
652 if os.path.isfile(fext):
656 elif os.name == 'os2':
658 def WhereIs(file, path=None, pathext=None):
660 path = os.environ['PATH']
662 path = string.split(path, os.pathsep)
664 pathext = ['.exe', '.cmd']
666 if string.lower(ext) == string.lower(file[-len(ext):]):
670 f = os.path.join(dir, file)
673 if os.path.isfile(fext):
679 def WhereIs(file, path=None, pathext=None):
681 path = os.environ['PATH']
683 path = string.split(path, os.pathsep)
685 f = os.path.join(dir, file)
686 if os.path.isfile(f):
691 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
695 def ParseConfig(env, command, function=None):
696 """Use the specified function to parse the output of the command in order
697 to modify the specified environment. The 'command' can be a string or a
698 list of strings representing a command and it's arguments. 'Function' is
699 an optional argument that takes the environment and the output of the
700 command. If no function is specified, the output will be treated as the
701 output of a typical 'X-config' command (i.e. gtk-config) and used to set
702 the CPPPATH, LIBPATH, LIBS, and CCFLAGS variables.
704 # the default parse function
705 def parse_conf(env, output):
706 env_dict = env.Dictionary()
709 # setup all the dictionary options
710 if not env_dict.has_key('CPPPATH'):
711 env_dict['CPPPATH'] = []
712 if not env_dict.has_key('LIBPATH'):
713 env_dict['LIBPATH'] = []
714 if not env_dict.has_key('LIBS'):
715 env_dict['LIBS'] = []
716 if not env_dict.has_key('CCFLAGS') or env_dict['CCFLAGS'] == "":
717 env_dict['CCFLAGS'] = []
719 params = string.split(output)
725 env_dict['LIBPATH'].append(arg[2:])
727 env_dict['LIBS'].append(arg[2:])
729 env_dict['CPPPATH'].append(arg[2:])
731 env_dict['CCFLAGS'].append(arg)
733 static_libs.append(arg)
737 function = parse_conf
738 if type(command) is type([]):
739 command = string.join(command)
740 return function(env, os.popen(command).read())
742 def dir_index(directory):
744 for file in os.listdir(directory):
745 fullname = os.path.join(directory, file)
746 files.append(fullname)
749 def fs_delete(path, remove=1):
751 if os.path.exists(path):
752 if os.path.isfile(path):
753 if remove: os.unlink(path)
754 display("Removed " + path)
755 elif os.path.isdir(path) and not os.path.islink(path):
756 # delete everything in the dir
757 for p in dir_index(path):
758 if os.path.isfile(p):
759 if remove: os.unlink(p)
760 display("Removed " + p)
763 # then delete dir itself
764 if remove: os.rmdir(path)
765 display("Removed directory " + path)
767 print "scons: Could not remove '%s':" % str(path), e.strerror
769 display = DisplayEngine()