~vcs-imports/kupfer/master-new

« back to all changes in this revision

Viewing changes to waflib/Utils.py

  • Committer: Ulrik Sverdrup
  • Date: 2012-02-26 17:50:05 UTC
  • mfrom: (2916.1.5)
  • Revision ID: git-v1:a1d52c4a74cd48e1b673e68977eba58b48928b7f
Merge branch 'full-waf'

* full-waf:
  Update NEWS
  wscript: Use .xz for distribution tarball
  wscript: Clean all .pyc files on distclean
  Update README for Waf being included in the repository and tarball
  Add waf-light and waflib from waf-1.6.11

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# encoding: utf-8
 
3
# Thomas Nagy, 2005-2010 (ita)
 
4
 
 
5
"""
 
6
Utilities and platform-specific fixes
 
7
 
 
8
The portability fixes try to provide a consistent behavior of the Waf API
 
9
through Python versions 2.3 to 3.X and across different platforms (win32, linux, etc)
 
10
"""
 
11
 
 
12
import os, sys, errno, traceback, inspect, re, shutil, datetime, gc
 
13
try:
 
14
        import subprocess
 
15
except:
 
16
        try:
 
17
                import waflib.extras.subprocess as subprocess
 
18
        except:
 
19
                print("The subprocess module is missing (python2.3?):\n try calling 'waf update --files=subprocess'\n or add a copy of subprocess.py to the python libraries")
 
20
 
 
21
try:
 
22
        from collections import deque
 
23
except ImportError:
 
24
        class deque(list):
 
25
                """A deque for Python 2.3 which does not have one"""
 
26
                def popleft(self):
 
27
                        return self.pop(0)
 
28
try:
 
29
        import _winreg as winreg
 
30
except:
 
31
        try:
 
32
                import winreg
 
33
        except:
 
34
                winreg = None
 
35
 
 
36
from waflib import Errors
 
37
 
 
38
try:
 
39
        from collections import UserDict
 
40
except:
 
41
        from UserDict import UserDict
 
42
 
 
43
try:
 
44
        from hashlib import md5
 
45
except:
 
46
        try:
 
47
                from md5 import md5
 
48
        except:
 
49
                # never fail to enable fixes from another module
 
50
                pass
 
51
 
 
52
try:
 
53
        import threading
 
54
except:
 
55
        class threading(object):
 
56
                """
 
57
                        A fake threading class for platforms lacking the threading module.
 
58
                        Use ``waf -j1`` on those platforms
 
59
                """
 
60
                pass
 
61
        class Lock(object):
 
62
                """Fake Lock class"""
 
63
                def acquire(self):
 
64
                        pass
 
65
                def release(self):
 
66
                        pass
 
67
        threading.Lock = threading.Thread = Lock
 
68
else:
 
69
        run_old = threading.Thread.run
 
70
        def run(*args, **kwargs):
 
71
                try:
 
72
                        run_old(*args, **kwargs)
 
73
                except (KeyboardInterrupt, SystemExit):
 
74
                        raise
 
75
                except:
 
76
                        sys.excepthook(*sys.exc_info())
 
77
        threading.Thread.run = run
 
78
 
 
79
SIG_NIL = 'iluvcuteoverload'.encode()
 
80
"""Arbitrary null value for a md5 hash. This value must be changed when the hash value is replaced (size)"""
 
81
 
 
82
O644 = 420
 
83
"""Constant representing the permissions for regular files (0644 raises a syntax error on python 3)"""
 
84
 
 
85
O755 = 493
 
86
"""Constant representing the permissions for executable files (0755 raises a syntax error on python 3)"""
 
87
 
 
88
rot_chr = ['\\', '|', '/', '-']
 
89
"List of characters to use when displaying the throbber (progress bar)"
 
90
 
 
91
rot_idx = 0
 
92
"Index of the current throbber character (progress bar)"
 
93
 
 
94
try:
 
95
        from collections import defaultdict
 
96
except ImportError:
 
97
        class defaultdict(dict):
 
98
                """
 
99
                defaultdict was introduced in python 2.5, so we leave it for python 2.4 and 2.3
 
100
                """
 
101
                def __init__(self, default_factory):
 
102
                        super(defaultdict, self).__init__()
 
103
                        self.default_factory = default_factory
 
104
                def __getitem__(self, key):
 
105
                        try:
 
106
                                return super(defaultdict, self).__getitem__(key)
 
107
                        except KeyError:
 
108
                                value = self.default_factory()
 
109
                                self[key] = value
 
110
                                return value
 
111
 
 
112
is_win32 = sys.platform in ('win32', 'cli')
 
113
 
 
114
# we should have put this in the Logs.py file instead :-/
 
115
indicator = '\x1b[K%s%s%s\r'
 
116
if is_win32 and 'NOCOLOR' in os.environ:
 
117
        indicator = '%s%s%s\r'
 
118
 
 
119
def readf(fname, m='r'):
 
120
        """
 
121
        Read an entire file into a string, in practice the wrapper
 
122
        node.read(..) should be used instead of this method::
 
123
 
 
124
                def build(ctx):
 
125
                        from waflib import Utils
 
126
                        txt = Utils.readf(self.path.find_node('wscript').abspath())
 
127
                        txt = ctx.path.find_node('wscript').read()
 
128
 
 
129
        :type  fname: string
 
130
        :param fname: Path to file
 
131
        :type  m: string
 
132
        :param m: Open mode
 
133
        :rtype: string
 
134
        :return: Content of the file
 
135
        """
 
136
        f = open(fname, m)
 
137
        try:
 
138
                txt = f.read()
 
139
        finally:
 
140
                f.close()
 
141
        return txt
 
142
 
 
143
def h_file(filename):
 
144
        """
 
145
        Compute a hash value for a file by using md5. This method may be replaced by
 
146
        a faster version if necessary. The following uses the file size and the timestamp value::
 
147
 
 
148
                import stat
 
149
                from waflib import Utils
 
150
                def h_file(filename):
 
151
                        st = os.stat(filename)
 
152
                        if stat.S_ISDIR(st[stat.ST_MODE]): raise IOError('not a file')
 
153
                        m = Utils.md5()
 
154
                        m.update(str(st.st_mtime))
 
155
                        m.update(str(st.st_size))
 
156
                        m.update(filename)
 
157
                        return m.digest()
 
158
                Utils.h_file = h_file
 
159
 
 
160
        :type filename: string
 
161
        :param filename: path to the file to hash
 
162
        :return: hash of the file contents
 
163
        """
 
164
        f = open(filename, 'rb')
 
165
        m = md5()
 
166
        try:
 
167
                while filename:
 
168
                        filename = f.read(100000)
 
169
                        m.update(filename)
 
170
        finally:
 
171
                f.close()
 
172
        return m.digest()
 
173
 
 
174
try:
 
175
        x = ''.encode('hex')
 
176
except:
 
177
        import binascii
 
178
        def to_hex(s):
 
179
                ret = binascii.hexlify(s)
 
180
                if not isinstance(ret, str):
 
181
                        ret = ret.decode('utf-8')
 
182
                return ret
 
183
else:
 
184
        def to_hex(s):
 
185
                return s.encode('hex')
 
186
 
 
187
to_hex.__doc__ = """
 
188
Return the hexadecimal representation of a string
 
189
 
 
190
:param s: string to convert
 
191
:type s: string
 
192
"""
 
193
 
 
194
listdir = os.listdir
 
195
if is_win32:
 
196
        def listdir_win32(s):
 
197
                """
 
198
                List the contents of a folder in a portable manner.
 
199
 
 
200
                :type s: string
 
201
                :param s: a string, which can be empty on Windows for listing the drive letters
 
202
                """
 
203
                if not s:
 
204
                        try:
 
205
                                import ctypes
 
206
                        except:
 
207
                                # there is nothing much we can do
 
208
                                return [x + ':\\' for x in list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')]
 
209
                        else:
 
210
                                dlen = 4 # length of "?:\\x00"
 
211
                                maxdrives = 26
 
212
                                buf = ctypes.create_string_buffer(maxdrives * dlen)
 
213
                                ndrives = ctypes.windll.kernel32.GetLogicalDriveStringsA(maxdrives, ctypes.byref(buf))
 
214
                                return [ buf.raw[4*i:4*i+3].decode('ascii') for i in range(int(ndrives/dlen)) ]
 
215
 
 
216
                if len(s) == 2 and s[1] == ":":
 
217
                        s += os.sep
 
218
 
 
219
                if not os.path.isdir(s):
 
220
                        e = OSError()
 
221
                        e.errno = errno.ENOENT
 
222
                        raise e
 
223
                return os.listdir(s)
 
224
        listdir = listdir_win32
 
225
 
 
226
def num2ver(ver):
 
227
        """
 
228
        Convert a string, tuple or version number into an integer. The number is supposed to have at most 4 digits::
 
229
 
 
230
                from waflib.Utils import num2ver
 
231
                num2ver('1.3.2') == num2ver((1,3,2)) == num2ver((1,3,2,0))
 
232
 
 
233
        :type ver: string or tuple of numbers
 
234
        :param ver: a version number
 
235
        """
 
236
        if isinstance(ver, str):
 
237
                ver = tuple(ver.split('.'))
 
238
        if isinstance(ver, tuple):
 
239
                ret = 0
 
240
                for i in range(4):
 
241
                        if i < len(ver):
 
242
                                ret += 256**(3 - i) * int(ver[i])
 
243
                return ret
 
244
        return ver
 
245
 
 
246
def ex_stack():
 
247
        """
 
248
        Extract the stack to display exceptions
 
249
 
 
250
        :return: a string represening the last exception
 
251
        """
 
252
        exc_type, exc_value, tb = sys.exc_info()
 
253
        exc_lines = traceback.format_exception(exc_type, exc_value, tb)
 
254
        return ''.join(exc_lines)
 
255
 
 
256
def to_list(sth):
 
257
        """
 
258
        Convert a string argument to a list by splitting on spaces, and pass
 
259
        through a list argument unchanged::
 
260
 
 
261
                from waflib.Utils import to_list
 
262
                lst = to_list("a b c d")
 
263
 
 
264
        :param sth: List or a string of items separated by spaces
 
265
        :rtype: list
 
266
        :return: Argument converted to list
 
267
 
 
268
        """
 
269
        if isinstance(sth, str):
 
270
                return sth.split()
 
271
        else:
 
272
                return sth
 
273
 
 
274
re_nl = re.compile('\r*\n', re.M)
 
275
def str_to_dict(txt):
 
276
        """
 
277
        Parse a string with key = value pairs into a dictionary::
 
278
 
 
279
                from waflib import Utils
 
280
                x = Utils.str_to_dict('''
 
281
                        a = 1
 
282
                        b = test
 
283
                ''')
 
284
 
 
285
        :type  s: string
 
286
        :param s: String to parse
 
287
        :rtype: dict
 
288
        :return: Dictionary containing parsed key-value pairs
 
289
        """
 
290
        tbl = {}
 
291
 
 
292
        lines = re_nl.split(txt)
 
293
        for x in lines:
 
294
                x = x.strip()
 
295
                if not x or x.startswith('#') or x.find('=') < 0:
 
296
                        continue
 
297
                tmp = x.split('=')
 
298
                tbl[tmp[0].strip()] = '='.join(tmp[1:]).strip()
 
299
        return tbl
 
300
 
 
301
def split_path(path):
 
302
        return path.split('/')
 
303
 
 
304
def split_path_cygwin(path):
 
305
        if path.startswith('//'):
 
306
                ret = path.split('/')[2:]
 
307
                ret[0] = '/' + ret[0]
 
308
                return ret
 
309
        return path.split('/')
 
310
 
 
311
re_sp = re.compile('[/\\\\]')
 
312
def split_path_win32(path):
 
313
        if path.startswith('\\\\'):
 
314
                ret = re.split(re_sp, path)[2:]
 
315
                ret[0] = '\\' + ret[0]
 
316
                return ret
 
317
        return re.split(re_sp, path)
 
318
 
 
319
if sys.platform == 'cygwin':
 
320
        split_path = split_path_cygwin
 
321
elif is_win32:
 
322
        split_path = split_path_win32
 
323
 
 
324
split_path.__doc__ = """
 
325
Split a path by / or \\. This function is not like os.path.split
 
326
 
 
327
:type  path: string
 
328
:param path: path to split
 
329
:return:     list of strings
 
330
"""
 
331
 
 
332
def check_dir(path):
 
333
        """
 
334
        Ensure that a directory exists (similar to ``mkdir -p``).
 
335
 
 
336
        :type  dir: string
 
337
        :param dir: Path to directory
 
338
        """
 
339
        if not os.path.isdir(path):
 
340
                try:
 
341
                        os.makedirs(path)
 
342
                except OSError as e:
 
343
                        if not os.path.isdir(path):
 
344
                                raise Errors.WafError('Cannot create the folder %r' % path, ex=e)
 
345
 
 
346
def def_attrs(cls, **kw):
 
347
        """
 
348
        Set default attributes on a class instance
 
349
 
 
350
        :type cls: class
 
351
        :param cls: the class to update the given attributes in.
 
352
        :type kw: dict
 
353
        :param kw: dictionary of attributes names and values.
 
354
        """
 
355
        for k, v in kw.items():
 
356
                if not hasattr(cls, k):
 
357
                        setattr(cls, k, v)
 
358
 
 
359
def quote_define_name(s):
 
360
        """
 
361
        Convert a string to an identifier suitable for C defines.
 
362
 
 
363
        :type  s: string
 
364
        :param s: String to convert
 
365
        :rtype: string
 
366
        :return: Identifier suitable for C defines
 
367
        """
 
368
        fu = re.compile("[^a-zA-Z0-9]").sub("_", s)
 
369
        fu = fu.upper()
 
370
        return fu
 
371
 
 
372
def h_list(lst):
 
373
        """
 
374
        Hash lists. For tuples, using hash(tup) is much more efficient
 
375
 
 
376
        :param lst: list to hash
 
377
        :type lst: list of strings
 
378
        :return: hash of the list
 
379
        """
 
380
        m = md5()
 
381
        m.update(str(lst).encode())
 
382
        return m.digest()
 
383
 
 
384
def h_fun(fun):
 
385
        """
 
386
        Hash functions
 
387
 
 
388
        :param fun: function to hash
 
389
        :type  fun: function
 
390
        :return: hash of the function
 
391
        """
 
392
        try:
 
393
                return fun.code
 
394
        except AttributeError:
 
395
                try:
 
396
                        h = inspect.getsource(fun)
 
397
                except IOError:
 
398
                        h = "nocode"
 
399
                try:
 
400
                        fun.code = h
 
401
                except AttributeError:
 
402
                        pass
 
403
                return h
 
404
 
 
405
reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
 
406
def subst_vars(expr, params):
 
407
        """
 
408
        Replace ${VAR} with the value of VAR taken from a dict or a config set::
 
409
 
 
410
                from waflib import Utils
 
411
                s = Utils.subst_vars('${PREFIX}/bin', env)
 
412
 
 
413
        :type  expr: string
 
414
        :param expr: String to perform substitution on
 
415
        :param params: Dictionary or config set to look up variable values.
 
416
        """
 
417
        def repl_var(m):
 
418
                if m.group(1):
 
419
                        return '\\'
 
420
                if m.group(2):
 
421
                        return '$'
 
422
                try:
 
423
                        # ConfigSet instances may contain lists
 
424
                        return params.get_flat(m.group(3))
 
425
                except AttributeError:
 
426
                        return params[m.group(3)]
 
427
        return reg_subst.sub(repl_var, expr)
 
428
 
 
429
def destos_to_binfmt(key):
 
430
        """
 
431
        Return the binary format based on the unversioned platform name.
 
432
 
 
433
        :param key: platform name
 
434
        :type  key: string
 
435
        :return: string representing the binary format
 
436
        """
 
437
        if key == 'darwin':
 
438
                return 'mac-o'
 
439
        elif key in ('win32', 'cygwin', 'uwin', 'msys'):
 
440
                return 'pe'
 
441
        return 'elf'
 
442
 
 
443
def unversioned_sys_platform():
 
444
        """
 
445
        Return the unversioned platform name.
 
446
        Some Python platform names contain versions, that depend on
 
447
        the build environment, e.g. linux2, freebsd6, etc.
 
448
        This returns the name without the version number. Exceptions are
 
449
        os2 and win32, which are returned verbatim.
 
450
 
 
451
        :rtype: string
 
452
        :return: Unversioned platform name
 
453
        """
 
454
        s = sys.platform
 
455
        if s == 'java':
 
456
                # The real OS is hidden under the JVM.
 
457
                from java.lang import System
 
458
                s = System.getProperty('os.name')
 
459
                # see http://lopica.sourceforge.net/os.html for a list of possible values
 
460
                if s == 'Mac OS X':
 
461
                        return 'darwin'
 
462
                elif s.startswith('Windows '):
 
463
                        return 'win32'
 
464
                elif s == 'OS/2':
 
465
                        return 'os2'
 
466
                elif s == 'HP-UX':
 
467
                        return 'hpux'
 
468
                elif s in ('SunOS', 'Solaris'):
 
469
                        return 'sunos'
 
470
                else: s = s.lower()
 
471
        
 
472
        # powerpc == darwin for our purposes
 
473
        if s == 'powerpc':
 
474
                return 'darwin'
 
475
        if s == 'win32' or s.endswith('os2') and s != 'sunos2': return s
 
476
        return re.split('\d+$', s)[0]
 
477
 
 
478
def nada(*k, **kw):
 
479
        """
 
480
        A function that does nothing
 
481
 
 
482
        :return: None
 
483
        """
 
484
        pass
 
485
 
 
486
class Timer(object):
 
487
        """
 
488
        Simple object for timing the execution of commands.
 
489
        Its string representation is the current time::
 
490
 
 
491
                from waflib.Utils import Timer
 
492
                timer = Timer()
 
493
                a_few_operations()
 
494
                s = str(timer)
 
495
        """
 
496
        def __init__(self):
 
497
                self.start_time = datetime.datetime.utcnow()
 
498
 
 
499
        def __str__(self):
 
500
                delta = datetime.datetime.utcnow() - self.start_time
 
501
                days = int(delta.days)
 
502
                hours = delta.seconds // 3600
 
503
                minutes = (delta.seconds - hours * 3600) // 60
 
504
                seconds = delta.seconds - hours * 3600 - minutes * 60 + float(delta.microseconds) / 1000 / 1000
 
505
                result = ''
 
506
                if days:
 
507
                        result += '%dd' % days
 
508
                if days or hours:
 
509
                        result += '%dh' % hours
 
510
                if days or hours or minutes:
 
511
                        result += '%dm' % minutes
 
512
                return '%s%.3fs' % (result, seconds)
 
513
 
 
514
if is_win32:
 
515
        old = shutil.copy2
 
516
        def copy2(src, dst):
 
517
                """
 
518
                shutil.copy2 does not copy the file attributes on windows, so we
 
519
                hack into the shutil module to fix the problem
 
520
                """
 
521
                old(src, dst)
 
522
                shutil.copystat(src, dst)
 
523
        setattr(shutil, 'copy2', copy2)
 
524
 
 
525
if os.name == 'java':
 
526
        # Jython cannot disable the gc but they can enable it ... wtf?
 
527
        try:
 
528
                gc.disable()
 
529
                gc.enable()
 
530
        except NotImplementedError:
 
531
                gc.disable = gc.enable
 
532
 
 
533
def read_la_file(path):
 
534
        """
 
535
        Read property files, used by msvc.py
 
536
 
 
537
        :param path: file to read
 
538
        :type path: string
 
539
        """
 
540
        sp = re.compile(r'^([^=]+)=\'(.*)\'$')
 
541
        dc = {}
 
542
        for line in readf(path).splitlines():
 
543
                try:
 
544
                        _, left, right, _ = sp.split(line.strip())
 
545
                        dc[left] = right
 
546
                except ValueError:
 
547
                        pass
 
548
        return dc
 
549
 
 
550
def nogc(fun):
 
551
        """
 
552
        Decorator: let a function disable the garbage collector during its execution.
 
553
        It is used in the build context when storing/loading the build cache file (pickle)
 
554
 
 
555
        :param fun: function to execute
 
556
        :type fun: function
 
557
        :return: the return value of the function executed
 
558
        """
 
559
        def f(*k, **kw):
 
560
                try:
 
561
                        gc.disable()
 
562
                        ret = fun(*k, **kw)
 
563
                finally:
 
564
                        gc.enable()
 
565
                return ret
 
566
        f.__doc__ = fun.__doc__
 
567
        return f
 
568
 
 
569
def run_once(fun):
 
570
        """
 
571
        Decorator: let a function cache its results, use like this::
 
572
 
 
573
                @run_once
 
574
                def foo(k):
 
575
                        return 345*2343
 
576
 
 
577
        :param fun: function to execute
 
578
        :type fun: function
 
579
        :return: the return value of the function executed
 
580
        """
 
581
        cache = {}
 
582
        def wrap(k):
 
583
                try:
 
584
                        return cache[k]
 
585
                except KeyError:
 
586
                        ret = fun(k)
 
587
                        cache[k] = ret
 
588
                        return ret
 
589
        wrap.__cache__ = cache
 
590
        return wrap
 
591
 
 
592
def get_registry_app_path(key, filename):
 
593
        if not winreg:
 
594
                return None
 
595
        try:
 
596
                result = winreg.QueryValue(key, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe" % filename[0])
 
597
        except WindowsError:
 
598
                pass
 
599
        else:
 
600
                if os.path.isfile(result):
 
601
                        return result
 
602