Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(409)

Side by Side Diff: scons-2.0.1/engine/SCons/Util.py

Issue 6711079: Added an unmodified copy of SCons to third_party. (Closed) Base URL: svn://svn.chromium.org/native_client/trunk/src/third_party/
Patch Set: '' Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 """SCons.Util
2
3 Various utility functions go here.
4 """
5 #
6 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The S Cons Foundation
7 #
8 # Permission is hereby granted, free of charge, to any person obtaining
9 # a copy of this software and associated documentation files (the
10 # "Software"), to deal in the Software without restriction, including
11 # without limitation the rights to use, copy, modify, merge, publish,
12 # distribute, sublicense, and/or sell copies of the Software, and to
13 # permit persons to whom the Software is furnished to do so, subject to
14 # the following conditions:
15 #
16 # The above copyright notice and this permission notice shall be included
17 # in all copies or substantial portions of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
20 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
21 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27 __revision__ = "src/engine/SCons/Util.py 5134 2010/08/16 23:02:40 bdeegan"
28
29 import os
30 import sys
31 import copy
32 import re
33 import types
34
35 from collections import UserDict, UserList, UserString
36
37 # Don't "from types import ..." these because we need to get at the
38 # types module later to look for UnicodeType.
39 InstanceType = types.InstanceType
40 MethodType = types.MethodType
41 FunctionType = types.FunctionType
42 try: unicode
43 except NameError: UnicodeType = None
44 else: UnicodeType = unicode
45
46 def dictify(keys, values, result={}):
47 for k, v in zip(keys, values):
48 result[k] = v
49 return result
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 if _altsep:
56 def rightmost_separator(path, sep):
57 return max(path.rfind(sep), path.rfind(_altsep))
58 else:
59 def rightmost_separator(path, sep):
60 return path.rfind(sep)
61
62 # First two from the Python Cookbook, just for completeness.
63 # (Yeah, yeah, YAGNI...)
64 def containsAny(str, set):
65 """Check whether sequence str contains ANY of the items in set."""
66 for c in set:
67 if c in str: return 1
68 return 0
69
70 def containsAll(str, set):
71 """Check whether sequence str contains ALL of the items in set."""
72 for c in set:
73 if c not in str: return 0
74 return 1
75
76 def containsOnly(str, set):
77 """Check whether sequence str contains ONLY items in set."""
78 for c in str:
79 if c not in set: return 0
80 return 1
81
82 def splitext(path):
83 "Same as os.path.splitext() but faster."
84 sep = rightmost_separator(path, os.sep)
85 dot = path.rfind('.')
86 # An ext is only real if it has at least one non-digit char
87 if dot > sep and not containsOnly(path[dot:], "0123456789."):
88 return path[:dot],path[dot:]
89 else:
90 return path,""
91
92 def updrive(path):
93 """
94 Make the drive letter (if any) upper case.
95 This is useful because Windows is inconsitent on the case
96 of the drive letter, which can cause inconsistencies when
97 calculating command signatures.
98 """
99 drive, rest = os.path.splitdrive(path)
100 if drive:
101 path = drive.upper() + rest
102 return path
103
104 class NodeList(UserList):
105 """This class is almost exactly like a regular list of Nodes
106 (actually it can hold any object), with one important difference.
107 If you try to get an attribute from this list, it will return that
108 attribute from every item in the list. For example:
109
110 >>> someList = NodeList([ ' foo ', ' bar ' ])
111 >>> someList.strip()
112 [ 'foo', 'bar' ]
113 """
114 def __nonzero__(self):
115 return len(self.data) != 0
116
117 def __str__(self):
118 return ' '.join(map(str, self.data))
119
120 def __iter__(self):
121 return iter(self.data)
122
123 def __call__(self, *args, **kwargs):
124 result = [x(*args, **kwargs) for x in self.data]
125 return self.__class__(result)
126
127 def __getattr__(self, name):
128 result = [getattr(x, name) for x in self.data]
129 return self.__class__(result)
130
131
132 _get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
133
134 def get_environment_var(varstr):
135 """Given a string, first determine if it looks like a reference
136 to a single environment variable, like "$FOO" or "${FOO}".
137 If so, return that variable with no decorations ("FOO").
138 If not, return None."""
139 mo=_get_env_var.match(to_String(varstr))
140 if mo:
141 var = mo.group(1)
142 if var[0] == '{':
143 return var[1:-1]
144 else:
145 return var
146 else:
147 return None
148
149 class DisplayEngine(object):
150 print_it = True
151 def __call__(self, text, append_newline=1):
152 if not self.print_it:
153 return
154 if append_newline: text = text + '\n'
155 try:
156 sys.stdout.write(unicode(text))
157 except IOError:
158 # Stdout might be connected to a pipe that has been closed
159 # by now. The most likely reason for the pipe being closed
160 # is that the user has press ctrl-c. It this is the case,
161 # then SCons is currently shutdown. We therefore ignore
162 # IOError's here so that SCons can continue and shutdown
163 # properly so that the .sconsign is correctly written
164 # before SCons exits.
165 pass
166
167 def set_mode(self, mode):
168 self.print_it = mode
169
170 def render_tree(root, child_func, prune=0, margin=[0], visited={}):
171 """
172 Render a tree of nodes into an ASCII tree view.
173 root - the root node of the tree
174 child_func - the function called to get the children of a node
175 prune - don't visit the same node twice
176 margin - the format of the left margin to use for children of root.
177 1 results in a pipe, and 0 results in no pipe.
178 visited - a dictionary of visited nodes in the current branch if not prune,
179 or in the whole tree if prune.
180 """
181
182 rname = str(root)
183
184 children = child_func(root)
185 retval = ""
186 for pipe in margin[:-1]:
187 if pipe:
188 retval = retval + "| "
189 else:
190 retval = retval + " "
191
192 if rname in visited:
193 return retval + "+-[" + rname + "]\n"
194
195 retval = retval + "+-" + rname + "\n"
196 if not prune:
197 visited = copy.copy(visited)
198 visited[rname] = 1
199
200 for i in range(len(children)):
201 margin.append(i<len(children)-1)
202 retval = retval + render_tree(children[i], child_func, prune, margin, vi sited
203 )
204 margin.pop()
205
206 return retval
207
208 IDX = lambda N: N and 1 or 0
209
210 def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}):
211 """
212 Print a tree of nodes. This is like render_tree, except it prints
213 lines directly instead of creating a string representation in memory,
214 so that huge trees can be printed.
215
216 root - the root node of the tree
217 child_func - the function called to get the children of a node
218 prune - don't visit the same node twice
219 showtags - print status information to the left of each node line
220 margin - the format of the left margin to use for children of root.
221 1 results in a pipe, and 0 results in no pipe.
222 visited - a dictionary of visited nodes in the current branch if not prune,
223 or in the whole tree if prune.
224 """
225
226 rname = str(root)
227
228 if showtags:
229
230 if showtags == 2:
231 legend = (' E = exists\n' +
232 ' R = exists in repository only\n' +
233 ' b = implicit builder\n' +
234 ' B = explicit builder\n' +
235 ' S = side effect\n' +
236 ' P = precious\n' +
237 ' A = always build\n' +
238 ' C = current\n' +
239 ' N = no clean\n' +
240 ' H = no cache\n' +
241 '\n')
242 sys.stdout.write(unicode(legend))
243
244 tags = ['[']
245 tags.append(' E'[IDX(root.exists())])
246 tags.append(' R'[IDX(root.rexists() and not root.exists())])
247 tags.append(' BbB'[[0,1][IDX(root.has_explicit_builder())] +
248 [0,2][IDX(root.has_builder())]])
249 tags.append(' S'[IDX(root.side_effect)])
250 tags.append(' P'[IDX(root.precious)])
251 tags.append(' A'[IDX(root.always_build)])
252 tags.append(' C'[IDX(root.is_up_to_date())])
253 tags.append(' N'[IDX(root.noclean)])
254 tags.append(' H'[IDX(root.nocache)])
255 tags.append(']')
256
257 else:
258 tags = []
259
260 def MMM(m):
261 return [" ","| "][m]
262 margins = list(map(MMM, margin[:-1]))
263
264 children = child_func(root)
265
266 if prune and rname in visited and children:
267 sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + u'\n')
268 return
269
270 sys.stdout.write(''.join(tags + margins + ['+-', rname]) + u'\n')
271
272 visited[rname] = 1
273
274 if children:
275 margin.append(1)
276 idx = IDX(showtags)
277 for C in children[:-1]:
278 print_tree(C, child_func, prune, idx, margin, visited)
279 margin[-1] = 0
280 print_tree(children[-1], child_func, prune, idx, margin, visited)
281 margin.pop()
282
283
284
285 # Functions for deciding if things are like various types, mainly to
286 # handle UserDict, UserList and UserString like their underlying types.
287 #
288 # Yes, all of this manual testing breaks polymorphism, and the real
289 # Pythonic way to do all of this would be to just try it and handle the
290 # exception, but handling the exception when it's not the right type is
291 # often too slow.
292
293 # We are using the following trick to speed up these
294 # functions. Default arguments are used to take a snapshot of the
295 # the global functions and constants used by these functions. This
296 # transforms accesses to global variable into local variables
297 # accesses (i.e. LOAD_FAST instead of LOAD_GLOBAL).
298
299 DictTypes = (dict, UserDict)
300 ListTypes = (list, UserList)
301 SequenceTypes = (list, tuple, UserList)
302
303 # Note that profiling data shows a speed-up when comparing
304 # explicitely with str and unicode instead of simply comparing
305 # with basestring. (at least on Python 2.5.1)
306 StringTypes = (str, unicode, UserString)
307
308 # Empirically, it is faster to check explicitely for str and
309 # unicode than for basestring.
310 BaseStringTypes = (str, unicode)
311
312 def is_Dict(obj, isinstance=isinstance, DictTypes=DictTypes):
313 return isinstance(obj, DictTypes)
314
315 def is_List(obj, isinstance=isinstance, ListTypes=ListTypes):
316 return isinstance(obj, ListTypes)
317
318 def is_Sequence(obj, isinstance=isinstance, SequenceTypes=SequenceTypes):
319 return isinstance(obj, SequenceTypes)
320
321 def is_Tuple(obj, isinstance=isinstance, tuple=tuple):
322 return isinstance(obj, tuple)
323
324 def is_String(obj, isinstance=isinstance, StringTypes=StringTypes):
325 return isinstance(obj, StringTypes)
326
327 def is_Scalar(obj, isinstance=isinstance, StringTypes=StringTypes, SequenceTypes =SequenceTypes):
328 # Profiling shows that there is an impressive speed-up of 2x
329 # when explicitely checking for strings instead of just not
330 # sequence when the argument (i.e. obj) is already a string.
331 # But, if obj is a not string then it is twice as fast to
332 # check only for 'not sequence'. The following code therefore
333 # assumes that the obj argument is a string must of the time.
334 return isinstance(obj, StringTypes) or not isinstance(obj, SequenceTypes)
335
336 def do_flatten(sequence, result, isinstance=isinstance,
337 StringTypes=StringTypes, SequenceTypes=SequenceTypes):
338 for item in sequence:
339 if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes):
340 result.append(item)
341 else:
342 do_flatten(item, result)
343
344 def flatten(obj, isinstance=isinstance, StringTypes=StringTypes,
345 SequenceTypes=SequenceTypes, do_flatten=do_flatten):
346 """Flatten a sequence to a non-nested list.
347
348 Flatten() converts either a single scalar or a nested sequence
349 to a non-nested list. Note that flatten() considers strings
350 to be scalars instead of sequences like Python would.
351 """
352 if isinstance(obj, StringTypes) or not isinstance(obj, SequenceTypes):
353 return [obj]
354 result = []
355 for item in obj:
356 if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes):
357 result.append(item)
358 else:
359 do_flatten(item, result)
360 return result
361
362 def flatten_sequence(sequence, isinstance=isinstance, StringTypes=StringTypes,
363 SequenceTypes=SequenceTypes, do_flatten=do_flatten):
364 """Flatten a sequence to a non-nested list.
365
366 Same as flatten(), but it does not handle the single scalar
367 case. This is slightly more efficient when one knows that
368 the sequence to flatten can not be a scalar.
369 """
370 result = []
371 for item in sequence:
372 if isinstance(item, StringTypes) or not isinstance(item, SequenceTypes):
373 result.append(item)
374 else:
375 do_flatten(item, result)
376 return result
377
378 # Generic convert-to-string functions that abstract away whether or
379 # not the Python we're executing has Unicode support. The wrapper
380 # to_String_for_signature() will use a for_signature() method if the
381 # specified object has one.
382 #
383 def to_String(s,
384 isinstance=isinstance, str=str,
385 UserString=UserString, BaseStringTypes=BaseStringTypes):
386 if isinstance(s,BaseStringTypes):
387 # Early out when already a string!
388 return s
389 elif isinstance(s, UserString):
390 # s.data can only be either a unicode or a regular
391 # string. Please see the UserString initializer.
392 return s.data
393 else:
394 return str(s)
395
396 def to_String_for_subst(s,
397 isinstance=isinstance, str=str, to_String=to_String,
398 BaseStringTypes=BaseStringTypes, SequenceTypes=SequenceT ypes,
399 UserString=UserString):
400
401 # Note that the test cases are sorted by order of probability.
402 if isinstance(s, BaseStringTypes):
403 return s
404 elif isinstance(s, SequenceTypes):
405 l = []
406 for e in s:
407 l.append(to_String_for_subst(e))
408 return ' '.join( s )
409 elif isinstance(s, UserString):
410 # s.data can only be either a unicode or a regular
411 # string. Please see the UserString initializer.
412 return s.data
413 else:
414 return str(s)
415
416 def to_String_for_signature(obj, to_String_for_subst=to_String_for_subst,
417 AttributeError=AttributeError):
418 try:
419 f = obj.for_signature
420 except AttributeError:
421 return to_String_for_subst(obj)
422 else:
423 return f()
424
425
426 # The SCons "semi-deep" copy.
427 #
428 # This makes separate copies of lists (including UserList objects)
429 # dictionaries (including UserDict objects) and tuples, but just copies
430 # references to anything else it finds.
431 #
432 # A special case is any object that has a __semi_deepcopy__() method,
433 # which we invoke to create the copy, which is used by the BuilderDict
434 # class because of its extra initialization argument.
435 #
436 # The dispatch table approach used here is a direct rip-off from the
437 # normal Python copy module.
438
439 _semi_deepcopy_dispatch = d = {}
440
441 def _semi_deepcopy_dict(x):
442 copy = {}
443 for key, val in x.items():
444 # The regular Python copy.deepcopy() also deepcopies the key,
445 # as follows:
446 #
447 # copy[semi_deepcopy(key)] = semi_deepcopy(val)
448 #
449 # Doesn't seem like we need to, but we'll comment it just in case.
450 copy[key] = semi_deepcopy(val)
451 return copy
452 d[dict] = _semi_deepcopy_dict
453
454 def _semi_deepcopy_list(x):
455 return list(map(semi_deepcopy, x))
456 d[list] = _semi_deepcopy_list
457
458 def _semi_deepcopy_tuple(x):
459 return tuple(map(semi_deepcopy, x))
460 d[tuple] = _semi_deepcopy_tuple
461
462 def semi_deepcopy(x):
463 copier = _semi_deepcopy_dispatch.get(type(x))
464 if copier:
465 return copier(x)
466 else:
467 if hasattr(x, '__semi_deepcopy__'):
468 return x.__semi_deepcopy__()
469 elif isinstance(x, UserDict):
470 return x.__class__(_semi_deepcopy_dict(x))
471 elif isinstance(x, UserList):
472 return x.__class__(_semi_deepcopy_list(x))
473
474 return x
475
476
477
478 class Proxy(object):
479 """A simple generic Proxy class, forwarding all calls to
480 subject. So, for the benefit of the python newbie, what does
481 this really mean? Well, it means that you can take an object, let's
482 call it 'objA', and wrap it in this Proxy class, with a statement
483 like this
484
485 proxyObj = Proxy(objA),
486
487 Then, if in the future, you do something like this
488
489 x = proxyObj.var1,
490
491 since Proxy does not have a 'var1' attribute (but presumably objA does),
492 the request actually is equivalent to saying
493
494 x = objA.var1
495
496 Inherit from this class to create a Proxy.
497
498 Note that, with new-style classes, this does *not* work transparently
499 for Proxy subclasses that use special .__*__() method names, because
500 those names are now bound to the class, not the individual instances.
501 You now need to know in advance which .__*__() method names you want
502 to pass on to the underlying Proxy object, and specifically delegate
503 their calls like this:
504
505 class Foo(Proxy):
506 __str__ = Delegate('__str__')
507 """
508
509 def __init__(self, subject):
510 """Wrap an object as a Proxy object"""
511 self._subject = subject
512
513 def __getattr__(self, name):
514 """Retrieve an attribute from the wrapped object. If the named
515 attribute doesn't exist, AttributeError is raised"""
516 return getattr(self._subject, name)
517
518 def get(self):
519 """Retrieve the entire wrapped object"""
520 return self._subject
521
522 def __cmp__(self, other):
523 if issubclass(other.__class__, self._subject.__class__):
524 return cmp(self._subject, other)
525 return cmp(self.__dict__, other.__dict__)
526
527 class Delegate(object):
528 """A Python Descriptor class that delegates attribute fetches
529 to an underlying wrapped subject of a Proxy. Typical use:
530
531 class Foo(Proxy):
532 __str__ = Delegate('__str__')
533 """
534 def __init__(self, attribute):
535 self.attribute = attribute
536 def __get__(self, obj, cls):
537 if isinstance(obj, cls):
538 return getattr(obj._subject, self.attribute)
539 else:
540 return self
541
542 # attempt to load the windows registry module:
543 can_read_reg = 0
544 try:
545 import winreg
546
547 can_read_reg = 1
548 hkey_mod = winreg
549
550 RegOpenKeyEx = winreg.OpenKeyEx
551 RegEnumKey = winreg.EnumKey
552 RegEnumValue = winreg.EnumValue
553 RegQueryValueEx = winreg.QueryValueEx
554 RegError = winreg.error
555
556 except ImportError:
557 try:
558 import win32api
559 import win32con
560 can_read_reg = 1
561 hkey_mod = win32con
562
563 RegOpenKeyEx = win32api.RegOpenKeyEx
564 RegEnumKey = win32api.RegEnumKey
565 RegEnumValue = win32api.RegEnumValue
566 RegQueryValueEx = win32api.RegQueryValueEx
567 RegError = win32api.error
568
569 except ImportError:
570 class _NoError(Exception):
571 pass
572 RegError = _NoError
573
574 if can_read_reg:
575 HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT
576 HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE
577 HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER
578 HKEY_USERS = hkey_mod.HKEY_USERS
579
580 def RegGetValue(root, key):
581 """This utility function returns a value in the registry
582 without having to open the key first. Only available on
583 Windows platforms with a version of Python that can read the
584 registry. Returns the same thing as
585 SCons.Util.RegQueryValueEx, except you just specify the entire
586 path to the value, and don't have to bother opening the key
587 first. So:
588
589 Instead of:
590 k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
591 r'SOFTWARE\Microsoft\Windows\CurrentVersion')
592 out = SCons.Util.RegQueryValueEx(k,
593 'ProgramFilesDir')
594
595 You can write:
596 out = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
597 r'SOFTWARE\Microsoft\Windows\CurrentVersion\ProgramFilesDir')
598 """
599 # I would use os.path.split here, but it's not a filesystem
600 # path...
601 p = key.rfind('\\') + 1
602 keyp = key[:p-1] # -1 to omit trailing slash
603 val = key[p:]
604 k = RegOpenKeyEx(root, keyp)
605 return RegQueryValueEx(k,val)
606 else:
607 try:
608 e = WindowsError
609 except NameError:
610 # Make sure we have a definition of WindowsError so we can
611 # run platform-independent tests of Windows functionality on
612 # platforms other than Windows. (WindowsError is, in fact, an
613 # OSError subclass on Windows.)
614 class WindowsError(OSError):
615 pass
616 import builtins
617 builtins.WindowsError = WindowsError
618 else:
619 del e
620
621 HKEY_CLASSES_ROOT = None
622 HKEY_LOCAL_MACHINE = None
623 HKEY_CURRENT_USER = None
624 HKEY_USERS = None
625
626 def RegGetValue(root, key):
627 raise WindowsError
628
629 def RegOpenKeyEx(root, key):
630 raise WindowsError
631
632 if sys.platform == 'win32':
633
634 def WhereIs(file, path=None, pathext=None, reject=[]):
635 if path is None:
636 try:
637 path = os.environ['PATH']
638 except KeyError:
639 return None
640 if is_String(path):
641 path = path.split(os.pathsep)
642 if pathext is None:
643 try:
644 pathext = os.environ['PATHEXT']
645 except KeyError:
646 pathext = '.COM;.EXE;.BAT;.CMD'
647 if is_String(pathext):
648 pathext = pathext.split(os.pathsep)
649 for ext in pathext:
650 if ext.lower() == file[-len(ext):].lower():
651 pathext = ['']
652 break
653 if not is_List(reject) and not is_Tuple(reject):
654 reject = [reject]
655 for dir in path:
656 f = os.path.join(dir, file)
657 for ext in pathext:
658 fext = f + ext
659 if os.path.isfile(fext):
660 try:
661 reject.index(fext)
662 except ValueError:
663 return os.path.normpath(fext)
664 continue
665 return None
666
667 elif os.name == 'os2':
668
669 def WhereIs(file, path=None, pathext=None, reject=[]):
670 if path is None:
671 try:
672 path = os.environ['PATH']
673 except KeyError:
674 return None
675 if is_String(path):
676 path = path.split(os.pathsep)
677 if pathext is None:
678 pathext = ['.exe', '.cmd']
679 for ext in pathext:
680 if ext.lower() == file[-len(ext):].lower():
681 pathext = ['']
682 break
683 if not is_List(reject) and not is_Tuple(reject):
684 reject = [reject]
685 for dir in path:
686 f = os.path.join(dir, file)
687 for ext in pathext:
688 fext = f + ext
689 if os.path.isfile(fext):
690 try:
691 reject.index(fext)
692 except ValueError:
693 return os.path.normpath(fext)
694 continue
695 return None
696
697 else:
698
699 def WhereIs(file, path=None, pathext=None, reject=[]):
700 import stat
701 if path is None:
702 try:
703 path = os.environ['PATH']
704 except KeyError:
705 return None
706 if is_String(path):
707 path = path.split(os.pathsep)
708 if not is_List(reject) and not is_Tuple(reject):
709 reject = [reject]
710 for d in path:
711 f = os.path.join(d, file)
712 if os.path.isfile(f):
713 try:
714 st = os.stat(f)
715 except OSError:
716 # os.stat() raises OSError, not IOError if the file
717 # doesn't exist, so in this case we let IOError get
718 # raised so as to not mask possibly serious disk or
719 # network issues.
720 continue
721 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
722 try:
723 reject.index(f)
724 except ValueError:
725 return os.path.normpath(f)
726 continue
727 return None
728
729 def PrependPath(oldpath, newpath, sep = os.pathsep,
730 delete_existing=1, canonicalize=None):
731 """This prepends newpath elements to the given oldpath. Will only
732 add any particular path once (leaving the first one it encounters
733 and ignoring the rest, to preserve path order), and will
734 os.path.normpath and os.path.normcase all paths to help assure
735 this. This can also handle the case where the given old path
736 variable is a list instead of a string, in which case a list will
737 be returned instead of a string.
738
739 Example:
740 Old Path: "/foo/bar:/foo"
741 New Path: "/biz/boom:/foo"
742 Result: "/biz/boom:/foo:/foo/bar"
743
744 If delete_existing is 0, then adding a path that exists will
745 not move it to the beginning; it will stay where it is in the
746 list.
747
748 If canonicalize is not None, it is applied to each element of
749 newpath before use.
750 """
751
752 orig = oldpath
753 is_list = 1
754 paths = orig
755 if not is_List(orig) and not is_Tuple(orig):
756 paths = paths.split(sep)
757 is_list = 0
758
759 if is_String(newpath):
760 newpaths = newpath.split(sep)
761 elif not is_List(newpath) and not is_Tuple(newpath):
762 newpaths = [ newpath ] # might be a Dir
763 else:
764 newpaths = newpath
765
766 if canonicalize:
767 newpaths=list(map(canonicalize, newpaths))
768
769 if not delete_existing:
770 # First uniquify the old paths, making sure to
771 # preserve the first instance (in Unix/Linux,
772 # the first one wins), and remembering them in normpaths.
773 # Then insert the new paths at the head of the list
774 # if they're not already in the normpaths list.
775 result = []
776 normpaths = []
777 for path in paths:
778 if not path:
779 continue
780 normpath = os.path.normpath(os.path.normcase(path))
781 if normpath not in normpaths:
782 result.append(path)
783 normpaths.append(normpath)
784 newpaths.reverse() # since we're inserting at the head
785 for path in newpaths:
786 if not path:
787 continue
788 normpath = os.path.normpath(os.path.normcase(path))
789 if normpath not in normpaths:
790 result.insert(0, path)
791 normpaths.append(normpath)
792 paths = result
793
794 else:
795 newpaths = newpaths + paths # prepend new paths
796
797 normpaths = []
798 paths = []
799 # now we add them only if they are unique
800 for path in newpaths:
801 normpath = os.path.normpath(os.path.normcase(path))
802 if path and not normpath in normpaths:
803 paths.append(path)
804 normpaths.append(normpath)
805
806 if is_list:
807 return paths
808 else:
809 return sep.join(paths)
810
811 def AppendPath(oldpath, newpath, sep = os.pathsep,
812 delete_existing=1, canonicalize=None):
813 """This appends new path elements to the given old path. Will
814 only add any particular path once (leaving the last one it
815 encounters and ignoring the rest, to preserve path order), and
816 will os.path.normpath and os.path.normcase all paths to help
817 assure this. This can also handle the case where the given old
818 path variable is a list instead of a string, in which case a list
819 will be returned instead of a string.
820
821 Example:
822 Old Path: "/foo/bar:/foo"
823 New Path: "/biz/boom:/foo"
824 Result: "/foo/bar:/biz/boom:/foo"
825
826 If delete_existing is 0, then adding a path that exists
827 will not move it to the end; it will stay where it is in the list.
828
829 If canonicalize is not None, it is applied to each element of
830 newpath before use.
831 """
832
833 orig = oldpath
834 is_list = 1
835 paths = orig
836 if not is_List(orig) and not is_Tuple(orig):
837 paths = paths.split(sep)
838 is_list = 0
839
840 if is_String(newpath):
841 newpaths = newpath.split(sep)
842 elif not is_List(newpath) and not is_Tuple(newpath):
843 newpaths = [ newpath ] # might be a Dir
844 else:
845 newpaths = newpath
846
847 if canonicalize:
848 newpaths=list(map(canonicalize, newpaths))
849
850 if not delete_existing:
851 # add old paths to result, then
852 # add new paths if not already present
853 # (I thought about using a dict for normpaths for speed,
854 # but it's not clear hashing the strings would be faster
855 # than linear searching these typically short lists.)
856 result = []
857 normpaths = []
858 for path in paths:
859 if not path:
860 continue
861 result.append(path)
862 normpaths.append(os.path.normpath(os.path.normcase(path)))
863 for path in newpaths:
864 if not path:
865 continue
866 normpath = os.path.normpath(os.path.normcase(path))
867 if normpath not in normpaths:
868 result.append(path)
869 normpaths.append(normpath)
870 paths = result
871 else:
872 # start w/ new paths, add old ones if not present,
873 # then reverse.
874 newpaths = paths + newpaths # append new paths
875 newpaths.reverse()
876
877 normpaths = []
878 paths = []
879 # now we add them only if they are unique
880 for path in newpaths:
881 normpath = os.path.normpath(os.path.normcase(path))
882 if path and not normpath in normpaths:
883 paths.append(path)
884 normpaths.append(normpath)
885 paths.reverse()
886
887 if is_list:
888 return paths
889 else:
890 return sep.join(paths)
891
892 if sys.platform == 'cygwin':
893 def get_native_path(path):
894 """Transforms an absolute path into a native path for the system. In
895 Cygwin, this converts from a Cygwin path to a Windows one."""
896 return os.popen('cygpath -w ' + path).read().replace('\n', '')
897 else:
898 def get_native_path(path):
899 """Transforms an absolute path into a native path for the system.
900 Non-Cygwin version, just leave the path alone."""
901 return path
902
903 display = DisplayEngine()
904
905 def Split(arg):
906 if is_List(arg) or is_Tuple(arg):
907 return arg
908 elif is_String(arg):
909 return arg.split()
910 else:
911 return [arg]
912
913 class CLVar(UserList):
914 """A class for command-line construction variables.
915
916 This is a list that uses Split() to split an initial string along
917 white-space arguments, and similarly to split any strings that get
918 added. This allows us to Do the Right Thing with Append() and
919 Prepend() (as well as straight Python foo = env['VAR'] + 'arg1
920 arg2') regardless of whether a user adds a list or a string to a
921 command-line construction variable.
922 """
923 def __init__(self, seq = []):
924 UserList.__init__(self, Split(seq))
925 def __add__(self, other):
926 return UserList.__add__(self, CLVar(other))
927 def __radd__(self, other):
928 return UserList.__radd__(self, CLVar(other))
929 def __coerce__(self, other):
930 return (self, CLVar(other))
931 def __str__(self):
932 return ' '.join(self.data)
933
934 # A dictionary that preserves the order in which items are added.
935 # Submitted by David Benjamin to ActiveState's Python Cookbook web site:
936 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
937 # Including fixes/enhancements from the follow-on discussions.
938 class OrderedDict(UserDict):
939 def __init__(self, dict = None):
940 self._keys = []
941 UserDict.__init__(self, dict)
942
943 def __delitem__(self, key):
944 UserDict.__delitem__(self, key)
945 self._keys.remove(key)
946
947 def __setitem__(self, key, item):
948 UserDict.__setitem__(self, key, item)
949 if key not in self._keys: self._keys.append(key)
950
951 def clear(self):
952 UserDict.clear(self)
953 self._keys = []
954
955 def copy(self):
956 dict = OrderedDict()
957 dict.update(self)
958 return dict
959
960 def items(self):
961 return list(zip(self._keys, list(self.values())))
962
963 def keys(self):
964 return self._keys[:]
965
966 def popitem(self):
967 try:
968 key = self._keys[-1]
969 except IndexError:
970 raise KeyError('dictionary is empty')
971
972 val = self[key]
973 del self[key]
974
975 return (key, val)
976
977 def setdefault(self, key, failobj = None):
978 UserDict.setdefault(self, key, failobj)
979 if key not in self._keys: self._keys.append(key)
980
981 def update(self, dict):
982 for (key, val) in dict.items():
983 self.__setitem__(key, val)
984
985 def values(self):
986 return list(map(self.get, self._keys))
987
988 class Selector(OrderedDict):
989 """A callable ordered dictionary that maps file suffixes to
990 dictionary values. We preserve the order in which items are added
991 so that get_suffix() calls always return the first suffix added."""
992 def __call__(self, env, source, ext=None):
993 if ext is None:
994 try:
995 ext = source[0].suffix
996 except IndexError:
997 ext = ""
998 try:
999 return self[ext]
1000 except KeyError:
1001 # Try to perform Environment substitution on the keys of
1002 # the dictionary before giving up.
1003 s_dict = {}
1004 for (k,v) in self.items():
1005 if k is not None:
1006 s_k = env.subst(k)
1007 if s_k in s_dict:
1008 # We only raise an error when variables point
1009 # to the same suffix. If one suffix is literal
1010 # and a variable suffix contains this literal,
1011 # the literal wins and we don't raise an error.
1012 raise KeyError(s_dict[s_k][0], k, s_k)
1013 s_dict[s_k] = (k,v)
1014 try:
1015 return s_dict[ext][1]
1016 except KeyError:
1017 try:
1018 return self[None]
1019 except KeyError:
1020 return None
1021
1022
1023 if sys.platform == 'cygwin':
1024 # On Cygwin, os.path.normcase() lies, so just report back the
1025 # fact that the underlying Windows OS is case-insensitive.
1026 def case_sensitive_suffixes(s1, s2):
1027 return 0
1028 else:
1029 def case_sensitive_suffixes(s1, s2):
1030 return (os.path.normcase(s1) != os.path.normcase(s2))
1031
1032 def adjustixes(fname, pre, suf, ensure_suffix=False):
1033 if pre:
1034 path, fn = os.path.split(os.path.normpath(fname))
1035 if fn[:len(pre)] != pre:
1036 fname = os.path.join(path, pre + fn)
1037 # Only append a suffix if the suffix we're going to add isn't already
1038 # there, and if either we've been asked to ensure the specific suffix
1039 # is present or there's no suffix on it at all.
1040 if suf and fname[-len(suf):] != suf and \
1041 (ensure_suffix or not splitext(fname)[1]):
1042 fname = fname + suf
1043 return fname
1044
1045
1046
1047 # From Tim Peters,
1048 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
1049 # ASPN: Python Cookbook: Remove duplicates from a sequence
1050 # (Also in the printed Python Cookbook.)
1051
1052 def unique(s):
1053 """Return a list of the elements in s, but without duplicates.
1054
1055 For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3],
1056 unique("abcabc") some permutation of ["a", "b", "c"], and
1057 unique(([1, 2], [2, 3], [1, 2])) some permutation of
1058 [[2, 3], [1, 2]].
1059
1060 For best speed, all sequence elements should be hashable. Then
1061 unique() will usually work in linear time.
1062
1063 If not possible, the sequence elements should enjoy a total
1064 ordering, and if list(s).sort() doesn't raise TypeError it's
1065 assumed that they do enjoy a total ordering. Then unique() will
1066 usually work in O(N*log2(N)) time.
1067
1068 If that's not possible either, the sequence elements must support
1069 equality-testing. Then unique() will usually work in quadratic
1070 time.
1071 """
1072
1073 n = len(s)
1074 if n == 0:
1075 return []
1076
1077 # Try using a dict first, as that's the fastest and will usually
1078 # work. If it doesn't work, it will usually fail quickly, so it
1079 # usually doesn't cost much to *try* it. It requires that all the
1080 # sequence elements be hashable, and support equality comparison.
1081 u = {}
1082 try:
1083 for x in s:
1084 u[x] = 1
1085 except TypeError:
1086 pass # move on to the next method
1087 else:
1088 return list(u.keys())
1089 del u
1090
1091 # We can't hash all the elements. Second fastest is to sort,
1092 # which brings the equal elements together; then duplicates are
1093 # easy to weed out in a single pass.
1094 # NOTE: Python's list.sort() was designed to be efficient in the
1095 # presence of many duplicate elements. This isn't true of all
1096 # sort functions in all languages or libraries, so this approach
1097 # is more effective in Python than it may be elsewhere.
1098 try:
1099 t = sorted(s)
1100 except TypeError:
1101 pass # move on to the next method
1102 else:
1103 assert n > 0
1104 last = t[0]
1105 lasti = i = 1
1106 while i < n:
1107 if t[i] != last:
1108 t[lasti] = last = t[i]
1109 lasti = lasti + 1
1110 i = i + 1
1111 return t[:lasti]
1112 del t
1113
1114 # Brute force is all that's left.
1115 u = []
1116 for x in s:
1117 if x not in u:
1118 u.append(x)
1119 return u
1120
1121
1122
1123 # From Alex Martelli,
1124 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
1125 # ASPN: Python Cookbook: Remove duplicates from a sequence
1126 # First comment, dated 2001/10/13.
1127 # (Also in the printed Python Cookbook.)
1128
1129 def uniquer(seq, idfun=None):
1130 if idfun is None:
1131 def idfun(x): return x
1132 seen = {}
1133 result = []
1134 for item in seq:
1135 marker = idfun(item)
1136 # in old Python versions:
1137 # if seen.has_key(marker)
1138 # but in new ones:
1139 if marker in seen: continue
1140 seen[marker] = 1
1141 result.append(item)
1142 return result
1143
1144 # A more efficient implementation of Alex's uniquer(), this avoids the
1145 # idfun() argument and function-call overhead by assuming that all
1146 # items in the sequence are hashable.
1147
1148 def uniquer_hashables(seq):
1149 seen = {}
1150 result = []
1151 for item in seq:
1152 #if not item in seen:
1153 if item not in seen:
1154 seen[item] = 1
1155 result.append(item)
1156 return result
1157
1158
1159
1160 # Much of the logic here was originally based on recipe 4.9 from the
1161 # Python CookBook, but we had to dumb it way down for Python 1.5.2.
1162 class LogicalLines(object):
1163
1164 def __init__(self, fileobj):
1165 self.fileobj = fileobj
1166
1167 def readline(self):
1168 result = []
1169 while True:
1170 line = self.fileobj.readline()
1171 if not line:
1172 break
1173 if line[-2:] == '\\\n':
1174 result.append(line[:-2])
1175 else:
1176 result.append(line)
1177 break
1178 return ''.join(result)
1179
1180 def readlines(self):
1181 result = []
1182 while True:
1183 line = self.readline()
1184 if not line:
1185 break
1186 result.append(line)
1187 return result
1188
1189
1190
1191 class UniqueList(UserList):
1192 def __init__(self, seq = []):
1193 UserList.__init__(self, seq)
1194 self.unique = True
1195 def __make_unique(self):
1196 if not self.unique:
1197 self.data = uniquer_hashables(self.data)
1198 self.unique = True
1199 def __lt__(self, other):
1200 self.__make_unique()
1201 return UserList.__lt__(self, other)
1202 def __le__(self, other):
1203 self.__make_unique()
1204 return UserList.__le__(self, other)
1205 def __eq__(self, other):
1206 self.__make_unique()
1207 return UserList.__eq__(self, other)
1208 def __ne__(self, other):
1209 self.__make_unique()
1210 return UserList.__ne__(self, other)
1211 def __gt__(self, other):
1212 self.__make_unique()
1213 return UserList.__gt__(self, other)
1214 def __ge__(self, other):
1215 self.__make_unique()
1216 return UserList.__ge__(self, other)
1217 def __cmp__(self, other):
1218 self.__make_unique()
1219 return UserList.__cmp__(self, other)
1220 def __len__(self):
1221 self.__make_unique()
1222 return UserList.__len__(self)
1223 def __getitem__(self, i):
1224 self.__make_unique()
1225 return UserList.__getitem__(self, i)
1226 def __setitem__(self, i, item):
1227 UserList.__setitem__(self, i, item)
1228 self.unique = False
1229 def __getslice__(self, i, j):
1230 self.__make_unique()
1231 return UserList.__getslice__(self, i, j)
1232 def __setslice__(self, i, j, other):
1233 UserList.__setslice__(self, i, j, other)
1234 self.unique = False
1235 def __add__(self, other):
1236 result = UserList.__add__(self, other)
1237 result.unique = False
1238 return result
1239 def __radd__(self, other):
1240 result = UserList.__radd__(self, other)
1241 result.unique = False
1242 return result
1243 def __iadd__(self, other):
1244 result = UserList.__iadd__(self, other)
1245 result.unique = False
1246 return result
1247 def __mul__(self, other):
1248 result = UserList.__mul__(self, other)
1249 result.unique = False
1250 return result
1251 def __rmul__(self, other):
1252 result = UserList.__rmul__(self, other)
1253 result.unique = False
1254 return result
1255 def __imul__(self, other):
1256 result = UserList.__imul__(self, other)
1257 result.unique = False
1258 return result
1259 def append(self, item):
1260 UserList.append(self, item)
1261 self.unique = False
1262 def insert(self, i):
1263 UserList.insert(self, i)
1264 self.unique = False
1265 def count(self, item):
1266 self.__make_unique()
1267 return UserList.count(self, item)
1268 def index(self, item):
1269 self.__make_unique()
1270 return UserList.index(self, item)
1271 def reverse(self):
1272 self.__make_unique()
1273 UserList.reverse(self)
1274 def sort(self, *args, **kwds):
1275 self.__make_unique()
1276 return UserList.sort(self, *args, **kwds)
1277 def extend(self, other):
1278 UserList.extend(self, other)
1279 self.unique = False
1280
1281
1282 class Unbuffered(object):
1283 """
1284 A proxy class that wraps a file object, flushing after every write,
1285 and delegating everything else to the wrapped object.
1286 """
1287 def __init__(self, file):
1288 self.file = file
1289 self.softspace = 0 ## backward compatibility; not supported in Py3k
1290 def write(self, arg):
1291 try:
1292 self.file.write(arg)
1293 self.file.flush()
1294 except IOError:
1295 # Stdout might be connected to a pipe that has been closed
1296 # by now. The most likely reason for the pipe being closed
1297 # is that the user has press ctrl-c. It this is the case,
1298 # then SCons is currently shutdown. We therefore ignore
1299 # IOError's here so that SCons can continue and shutdown
1300 # properly so that the .sconsign is correctly written
1301 # before SCons exits.
1302 pass
1303 def __getattr__(self, attr):
1304 return getattr(self.file, attr)
1305
1306 def make_path_relative(path):
1307 """ makes an absolute path name to a relative pathname.
1308 """
1309 if os.path.isabs(path):
1310 drive_s,path = os.path.splitdrive(path)
1311
1312 import re
1313 if not drive_s:
1314 path=re.compile("/*(.*)").findall(path)[0]
1315 else:
1316 path=path[1:]
1317
1318 assert( not os.path.isabs( path ) ), path
1319 return path
1320
1321
1322
1323 # The original idea for AddMethod() and RenameFunction() come from the
1324 # following post to the ActiveState Python Cookbook:
1325 #
1326 # ASPN: Python Cookbook : Install bound methods in an instance
1327 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/223613
1328 #
1329 # That code was a little fragile, though, so the following changes
1330 # have been wrung on it:
1331 #
1332 # * Switched the installmethod() "object" and "function" arguments,
1333 # so the order reflects that the left-hand side is the thing being
1334 # "assigned to" and the right-hand side is the value being assigned.
1335 #
1336 # * Changed explicit type-checking to the "try: klass = object.__class__"
1337 # block in installmethod() below so that it still works with the
1338 # old-style classes that SCons uses.
1339 #
1340 # * Replaced the by-hand creation of methods and functions with use of
1341 # the "new" module, as alluded to in Alex Martelli's response to the
1342 # following Cookbook post:
1343 #
1344 # ASPN: Python Cookbook : Dynamically added methods to a class
1345 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732
1346
1347 def AddMethod(obj, function, name=None):
1348 """
1349 Adds either a bound method to an instance or an unbound method to
1350 a class. If name is ommited the name of the specified function
1351 is used by default.
1352 Example:
1353 a = A()
1354 def f(self, x, y):
1355 self.z = x + y
1356 AddMethod(f, A, "add")
1357 a.add(2, 4)
1358 print a.z
1359 AddMethod(lambda self, i: self.l[i], a, "listIndex")
1360 print a.listIndex(5)
1361 """
1362 if name is None:
1363 name = function.func_name
1364 else:
1365 function = RenameFunction(function, name)
1366
1367 if hasattr(obj, '__class__') and obj.__class__ is not type:
1368 # "obj" is an instance, so it gets a bound method.
1369 setattr(obj, name, MethodType(function, obj, obj.__class__))
1370 else:
1371 # "obj" is a class, so it gets an unbound method.
1372 setattr(obj, name, MethodType(function, None, obj))
1373
1374 def RenameFunction(function, name):
1375 """
1376 Returns a function identical to the specified function, but with
1377 the specified name.
1378 """
1379 return FunctionType(function.func_code,
1380 function.func_globals,
1381 name,
1382 function.func_defaults)
1383
1384
1385 md5 = False
1386 def MD5signature(s):
1387 return str(s)
1388
1389 def MD5filesignature(fname, chunksize=65536):
1390 f = open(fname, "rb")
1391 result = f.read()
1392 f.close()
1393 return result
1394
1395 try:
1396 import hashlib
1397 except ImportError:
1398 pass
1399 else:
1400 if hasattr(hashlib, 'md5'):
1401 md5 = True
1402 def MD5signature(s):
1403 m = hashlib.md5()
1404 m.update(str(s))
1405 return m.hexdigest()
1406
1407 def MD5filesignature(fname, chunksize=65536):
1408 m = hashlib.md5()
1409 f = open(fname, "rb")
1410 while True:
1411 blck = f.read(chunksize)
1412 if not blck:
1413 break
1414 m.update(str(blck))
1415 f.close()
1416 return m.hexdigest()
1417
1418 def MD5collect(signatures):
1419 """
1420 Collects a list of signatures into an aggregate signature.
1421
1422 signatures - a list of signatures
1423 returns - the aggregate signature
1424 """
1425 if len(signatures) == 1:
1426 return signatures[0]
1427 else:
1428 return MD5signature(', '.join(signatures))
1429
1430
1431
1432 def silent_intern(x):
1433 """
1434 Perform sys.intern() on the passed argument and return the result.
1435 If the input is ineligible (e.g. a unicode string) the original argument is
1436 returned and no exception is thrown.
1437 """
1438 try:
1439 return sys.intern(x)
1440 except TypeError:
1441 return x
1442
1443
1444
1445 # From Dinu C. Gherman,
1446 # Python Cookbook, second edition, recipe 6.17, p. 277.
1447 # Also:
1448 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
1449 # ASPN: Python Cookbook: Null Object Design Pattern
1450
1451 #TODO??? class Null(object):
1452 class Null(object):
1453 """ Null objects always and reliably "do nothing." """
1454 def __new__(cls, *args, **kwargs):
1455 if not '_instance' in vars(cls):
1456 cls._instance = super(Null, cls).__new__(cls, *args, **kwargs)
1457 return cls._instance
1458 def __init__(self, *args, **kwargs):
1459 pass
1460 def __call__(self, *args, **kwargs):
1461 return self
1462 def __repr__(self):
1463 return "Null(0x%08X)" % id(self)
1464 def __nonzero__(self):
1465 return False
1466 def __getattr__(self, name):
1467 return self
1468 def __setattr__(self, name, value):
1469 return self
1470 def __delattr__(self, name):
1471 return self
1472
1473 class NullSeq(Null):
1474 def __len__(self):
1475 return 0
1476 def __iter__(self):
1477 return iter(())
1478 def __getitem__(self, i):
1479 return self
1480 def __delitem__(self, i):
1481 return self
1482 def __setitem__(self, i, v):
1483 return self
1484
1485
1486 del __revision__
1487
1488 # Local Variables:
1489 # tab-width:4
1490 # indent-tabs-mode:nil
1491 # End:
1492 # vim: set expandtab tabstop=4 shiftwidth=4:
OLDNEW
« no previous file with comments | « scons-2.0.1/engine/SCons/Tool/zip.py ('k') | scons-2.0.1/engine/SCons/Variables/BoolVariable.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698