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.lstat(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