3
# Thomas Nagy, 2005 (ita)
6
Utilities, the stable ones are the following:
8
* h_file: compute a unique value for a file (hash), it uses
9
the module fnv if it is installed (see waf/utils/fnv & http://code.google.com/p/waf/wiki/FAQ)
10
else, md5 (see the python docs)
12
For large projects (projects with more than 15000 files) or slow hard disks and filesystems (HFS)
13
it is possible to use a hashing based on the path and the size (may give broken cache results)
14
The method h_file MUST raise an OSError if the file is a folder
18
st = os.stat(filename)
19
if stat.S_ISDIR(st[stat.ST_MODE]): raise IOError('not a file')
21
m.update(str(st.st_mtime))
22
m.update(str(st.st_size))
26
To replace the function in your project, use something like this:
37
import os, sys, imp, string, errno, traceback, inspect, re, shutil, datetime, gc
39
# In python 3.0 we can get rid of all this
40
try: from UserDict import UserDict
41
except ImportError: from collections import UserDict
42
if sys.hexversion >= 0x2060000 or os.name == 'java':
43
import subprocess as pproc
47
from Constants import *
50
from collections import deque
56
is_win32 = sys.platform == 'win32'
59
# defaultdict in python 2.5
60
from collections import defaultdict as DefaultDict
62
class DefaultDict(dict):
63
def __init__(self, default_factory):
64
super(DefaultDict, self).__init__()
65
self.default_factory = default_factory
66
def __getitem__(self, key):
68
return super(DefaultDict, self).__getitem__(key)
70
value = self.default_factory()
74
class WafError(Exception):
75
def __init__(self, *args):
78
self.stack = traceback.extract_stack()
81
Exception.__init__(self, *args)
83
return str(len(self.args) == 1 and self.args[0] or self.args)
85
class WscriptError(WafError):
86
def __init__(self, message, wscript_file=None):
88
self.wscript_file = wscript_file
89
self.wscript_line = None
92
(self.wscript_file, self.wscript_line) = self.locate_error()
94
(self.wscript_file, self.wscript_line) = (None, None)
98
msg_file_line = "%s:" % self.wscript_file
100
msg_file_line += "%s:" % self.wscript_line
101
err_message = "%s error: %s" % (msg_file_line, message)
102
WafError.__init__(self, err_message)
104
def locate_error(self):
105
stack = traceback.extract_stack()
108
file_name = os.path.basename(frame[0])
109
is_wscript = (file_name == WSCRIPT_FILE or file_name == WSCRIPT_BUILD_FILE)
111
return (frame[0], frame[1])
114
indicator = is_win32 and '\x1b[A\x1b[K%s%s%s\r' or '\x1b[K%s%s%s\r'
117
from fnv import new as md5
119
Constants.SIG_NIL = 'signofnv'
121
def h_file(filename):
126
if x is None: raise OSError("not a file")
129
raise OSError("not a file" + filename)
134
from hashlib import md5
138
def h_file(filename):
139
f = open(filename, 'rb')
142
filename = f.read(100000)
147
# portability fixes may be added elsewhere (although, md5 should be everywhere by now)
150
class ordered_dict(UserDict):
151
def __init__(self, dict = None):
153
UserDict.__init__(self, dict)
155
def __delitem__(self, key):
156
self.allkeys.remove(key)
157
UserDict.__delitem__(self, key)
159
def __setitem__(self, key, item):
160
if key not in self.allkeys: self.allkeys.append(key)
161
UserDict.__setitem__(self, key, item)
163
def exec_command(s, **kw):
165
kw['stdout'] = kw['stderr'] = kw['log']
167
kw['shell'] = isinstance(s, str)
170
proc = pproc.Popen(s, **kw)
176
def exec_command(s, **kw):
178
kw['stdout'] = kw['stderr'] = kw['log']
180
kw['shell'] = isinstance(s, str)
183
startupinfo = pproc.STARTUPINFO()
184
startupinfo.dwFlags |= pproc.STARTF_USESHOWWINDOW
185
kw['startupinfo'] = startupinfo
188
if 'stdout' not in kw:
189
kw['stdout'] = pproc.PIPE
190
kw['stderr'] = pproc.PIPE
191
kw['universal_newlines'] = True
192
proc = pproc.Popen(s,**kw)
193
(stdout, stderr) = proc.communicate()
197
return proc.returncode
199
proc = pproc.Popen(s,**kw)
206
def listdir_win32(s):
207
if re.match('^[A-Za-z]:$', s):
208
# os.path.isdir fails if s contains only the drive name... (x:)
210
if not os.path.isdir(s):
212
e.errno = errno.ENOENT
215
listdir = listdir_win32
217
def waf_version(mini = 0x010000, maxi = 0x100000):
218
"Halts if the waf version is wrong"
220
try: min_val = mini + 0
221
except TypeError: min_val = int(mini.replace('.', '0'), 16)
224
Logs.error("waf version should be at least %s (%s found)" % (mini, ver))
227
try: max_val = maxi + 0
228
except TypeError: max_val = int(maxi.replace('.', '0'), 16)
231
Logs.error("waf version should be at most %s (%s found)" % (maxi, ver))
234
def python_24_guard():
235
if sys.hexversion < 0x20400f0 or sys.hexversion >= 0x3000000:
236
raise ImportError("Waf requires Python >= 2.3 but the raw source requires Python 2.4, 2.5 or 2.6")
239
exc_type, exc_value, tb = sys.exc_info()
241
exc_lines = traceback.format_exception(exc_type, exc_value, tb)
242
return ''.join(exc_lines)
243
return str(exc_value)
246
if isinstance(sth, str):
251
g_loaded_modules = {}
252
"index modules by absolute path"
255
"the main module is special"
257
def load_module(file_path, name=WSCRIPT_FILE):
258
"this function requires an absolute path"
260
return g_loaded_modules[file_path]
264
module = imp.new_module(name)
267
code = readf(file_path, m='rU')
268
except (IOError, OSError):
269
raise WscriptError('Could not read the file %r' % file_path)
271
module.waf_hash_val = code
273
dt = os.path.dirname(file_path)
274
sys.path.insert(0, dt)
276
exec(compile(code, file_path, 'exec'), module.__dict__)
278
exc_type, exc_value, tb = sys.exc_info()
279
raise WscriptError("".join(traceback.format_exception(exc_type, exc_value, tb)), file_path)
282
g_loaded_modules[file_path] = module
286
def set_main_module(file_path):
287
"Load custom options, if defined"
289
g_module = load_module(file_path, 'wscript_main')
290
g_module.root_path = file_path
295
g_module.APPNAME = 'noname'
299
g_module.VERSION = '1.0'
301
# note: to register the module globally, use the following:
302
# sys.modules['wscript_main'] = g_module
305
"used for importing env files"
309
if not line: continue
310
mems = line.split('=')
311
tbl[mems[0]] = mems[1]
318
import struct, fcntl, termios
324
dummy_lines, cols = struct.unpack("HHHH", \
325
fcntl.ioctl(sys.stderr.fileno(),termios.TIOCGWINSZ , \
326
struct.pack("HHHH", 0, 0, 0, 0)))[:2]
328
# we actually try the function once to see if it is suitable
334
get_term_cols = myfun
337
rot_chr = ['\\', '|', '/', '-']
338
"the rotation character in the progress bar"
341
def split_path(path):
342
return path.split('/')
344
def split_path_cygwin(path):
345
if path.startswith('//'):
346
ret = path.split('/')[2:]
347
ret[0] = '/' + ret[0]
349
return path.split('/')
351
re_sp = re.compile('[/\\\\]')
352
def split_path_win32(path):
353
if path.startswith('\\\\'):
354
ret = re.split(re_sp, path)[2:]
355
ret[0] = '\\' + ret[0]
357
return re.split(re_sp, path)
359
if sys.platform == 'cygwin':
360
split_path = split_path_cygwin
362
split_path = split_path_win32
364
def copy_attrs(orig, dest, names, only_if_set=False):
365
for a in to_list(names):
366
u = getattr(orig, a, ())
367
if u or not only_if_set:
370
def def_attrs(cls, **kw):
372
set attributes for class.
373
@param cls [any class]: the class to update the given attributes in.
374
@param kw [dictionary]: dictionary of attributes names and values.
376
if the given class hasn't one (or more) of these attributes, add the attribute with its value to the class.
378
for k, v in kw.iteritems():
379
if not hasattr(cls, k):
382
def quote_define_name(path):
383
fu = re.compile("[^a-zA-Z0-9]").sub("_", path)
387
def quote_whitespace(path):
388
return (path.strip().find(' ') > 0 and '"%s"' % path or path).replace('""', '"')
393
if s[0] == "'" and s[-1] == "'": return s[1:-1]
404
except AttributeError:
406
h = inspect.getsource(fun)
411
except AttributeError:
415
def pprint(col, str, label='', sep='\n'):
416
"print messages in color"
417
sys.stderr.write("%s%s%s %s%s" % (Logs.colors(col), str, Logs.colors.NORMAL, label, sep))
420
"""If a folder doesn't exists, create it."""
427
raise WafError("Cannot create folder '%s' (original error: %s)" % (dir, e))
429
def cmd_output(cmd, **kw):
433
silent = kw['silent']
441
kw['shell'] = isinstance(cmd, str)
442
kw['stdout'] = pproc.PIPE
444
kw['stderr'] = pproc.PIPE
447
p = pproc.Popen(cmd, **kw)
448
output = p.communicate()[0]
450
raise ValueError(str(e))
454
msg = "command execution failed: %s -> %r" % (cmd, str(output))
455
raise ValueError(msg)
459
reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}")
460
def subst_vars(expr, params):
461
"substitute ${PREFIX}/bin in /usr/local/bin"
468
# environments may contain lists
469
return params.get_flat(m.group(3))
470
except AttributeError:
471
return params[m.group(3)]
472
return reg_subst.sub(repl_var, expr)
474
def unversioned_sys_platform_to_binary_format(unversioned_sys_platform):
475
"infers the binary format from the unversioned_sys_platform name."
477
if unversioned_sys_platform in ('linux', 'freebsd', 'netbsd', 'openbsd', 'sunos', 'gnu'):
479
elif unversioned_sys_platform == 'darwin':
481
elif unversioned_sys_platform in ('win32', 'cygwin', 'uwin', 'msys'):
483
# TODO we assume all other operating systems are elf, which is not true.
484
# we may set this to 'unknown' and have ccroot and other tools handle the case "gracefully" (whatever that means).
487
def unversioned_sys_platform():
488
"""returns an unversioned name from sys.platform.
489
sys.plaform is not very well defined and depends directly on the python source tree.
490
The version appended to the names is unreliable as it's taken from the build environment at the time python was built,
491
i.e., it's possible to get freebsd7 on a freebsd8 system.
492
So we remove the version from the name, except for special cases where the os has a stupid name like os2 or win32.
493
Some possible values of sys.platform are, amongst others:
494
aix3 aix4 atheos beos5 darwin freebsd2 freebsd3 freebsd4 freebsd5 freebsd6 freebsd7
495
generic gnu0 irix5 irix6 linux2 mac netbsd1 next3 os2emx riscos sunos5 unixware7
496
Investigating the python source tree may reveal more values.
500
# The real OS is hidden under the JVM.
501
from java.lang import System
502
s = System.getProperty('os.name')
503
# see http://lopica.sourceforge.net/os.html for a list of possible values
506
elif s.startswith('Windows '):
512
elif s in ('SunOS', 'Solaris'):
515
if s == 'win32' or s.endswith('os2') and s != 'sunos2': return s
516
return re.split('\d+$', s)[0]
518
#@deprecated('use unversioned_sys_platform instead')
519
def detect_platform():
520
"""this function has been in the Utils module for some time.
521
It's hard to guess what people have used it for.
522
It seems its goal is to return an unversionned sys.platform, but it's not handling all platforms.
523
For example, the version is not removed on freebsd and netbsd, amongst others.
528
for x in 'cygwin linux irix sunos hpux aix darwin gnu'.split():
529
# sys.platform may be linux2
534
if os.name in 'posix java os2'.split():
539
def load_tool(tool, tooldir=None):
541
load_tool: import a Python module, optionally using several directories.
542
@param tool [string]: name of tool to import.
543
@param tooldir [list]: directories to look for the tool.
544
@return: the loaded module.
546
Warning: this function is not thread-safe: plays with sys.path,
547
so must run in sequence.
550
assert isinstance(tooldir, list)
551
sys.path = tooldir + sys.path
555
return __import__(tool)
560
def readf(fname, m='r'):
561
"get the contents of a file, it is not used anywhere for the moment"
570
"""A function that does nothing"""
573
def diff_path(top, subdir):
574
"""difference between two absolute paths"""
575
top = os.path.normpath(top).replace('\\', '/').split('/')
576
subdir = os.path.normpath(subdir).replace('\\', '/').split('/')
577
if len(top) == len(subdir): return ''
578
diff = subdir[len(top) - len(subdir):]
579
return os.path.join(*diff)
581
class Context(object):
582
"""A base class for commands to be executed from Waf scripts"""
584
def set_curdir(self, dir):
587
def get_curdir(self):
590
except AttributeError:
591
self.curdir_ = os.getcwd()
592
return self.get_curdir()
594
curdir = property(get_curdir, set_curdir)
596
def recurse(self, dirs, name=''):
597
"""The function for calling scripts from folders, it tries to call wscript + function_name
598
and if that file does not exist, it will call the method 'function_name' from a file named wscript
599
the dirs can be a list of folders or a string containing space-separated folder paths
602
name = inspect.stack()[1][3]
604
if isinstance(dirs, str):
611
nexdir = os.path.join(self.curdir, x)
613
base = os.path.join(nexdir, WSCRIPT_FILE)
614
file_path = base + '_' + name
617
txt = readf(file_path, m='rU')
618
except (OSError, IOError):
620
module = load_module(base)
622
raise WscriptError('No such script %s' % base)
625
f = module.__dict__[name]
627
raise WscriptError('No function %s defined in %s' % (name, base))
629
if getattr(self.__class__, 'pre_recurse', None):
630
self.pre_recurse(f, base, nexdir)
637
if getattr(self.__class__, 'post_recurse', None):
638
self.post_recurse(module, base, nexdir)
641
if getattr(self.__class__, 'pre_recurse', None):
642
dc = self.pre_recurse(txt, file_path, nexdir)
647
exec(compile(txt, file_path, 'exec'), dc)
649
exc_type, exc_value, tb = sys.exc_info()
650
raise WscriptError("".join(traceback.format_exception(exc_type, exc_value, tb)), base)
653
if getattr(self.__class__, 'post_recurse', None):
654
self.post_recurse(txt, file_path, nexdir)
660
shutil.copystat(src, src)
661
setattr(shutil, 'copy2', copy2)
663
def zip_folder(dir, zip_file_name, prefix):
665
prefix represents the app to add in the archive
668
zip = zipfile.ZipFile(zip_file_name, 'w', compression=zipfile.ZIP_DEFLATED)
669
base = os.path.abspath(dir)
672
if prefix[-1] != os.sep:
676
for root, dirs, files in os.walk(base):
678
archive_name = prefix + root[n:] + os.sep + f
679
zip.write(root + os.sep + f, archive_name, zipfile.ZIP_DEFLATED)
682
def get_elapsed_time(start):
683
"Format a time delta (datetime.timedelta) using the format DdHhMmS.MSs"
684
delta = datetime.datetime.now() - start
685
# cast to int necessary for python 3.0
686
days = int(delta.days)
687
hours = int(delta.seconds / 3600)
688
minutes = int((delta.seconds - hours * 3600) / 60)
689
seconds = delta.seconds - hours * 3600 - minutes * 60 \
690
+ float(delta.microseconds) / 1000 / 1000
693
result += '%dd' % days
695
result += '%dh' % hours
696
if days or hours or minutes:
697
result += '%dm' % minutes
698
return '%s%.3fs' % (result, seconds)
700
if os.name == 'java':
701
# For Jython (they should really fix the inconsistency)
705
except NotImplementedError:
706
gc.disable = gc.enable
710
decorator, make a function cache its results, use like this:
724
wrap.__cache__ = cache