1 # vi:filetype=python:expandtab:tabstop=4:shiftwidth=4
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
9 # Full author contact details are available in file CREDITS.
11 # This file defines all the utility functions for the
12 # scons-based build system of lyx
15 import os, sys, re, shutil, glob
16 from SCons.Util import *
19 def getVerFromConfigure(path):
20 " get lyx version from the AC_INIT line of configure.ac "
22 config = open(os.path.join(path, 'configure.ac'))
24 print "Can not open configure.ac. "
26 # find a line like follows
27 # AC_INIT(LyX,1.4.4svn,[lyx-devel@lists.lyx.org],[lyx])
28 pat = re.compile('AC_INIT\([^,]+,([^,]+),')
29 for line in config.readlines():
31 (version,) = pat.match(line).groups()
32 return version.strip()
36 def relativePath(path, base):
37 '''return relative path from base, which is usually top source dir'''
38 # full pathname of path
39 path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
40 path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
41 if path1[:len(path2)] != path2:
42 print "Path %s is not under top source directory" % path
43 if len(path2) == len(path1):
45 path3 = os.path.join(*path1[len(path2):]);
46 # replace all \ by / such that we get the same comments on Windows and *nix
47 path3 = path3.replace('\\', '/')
51 def isSubDir(path, base):
52 '''Whether or not path is a subdirectory of base'''
53 path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
54 path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
55 return len(path2) <= len(path1) and path1[:len(path2)] == path2
58 def writeToFile(filename, lines, append = False):
59 " utility function: write or append lines to filename "
60 # create directory if needed
61 dir = os.path.split(filename)[0]
62 if dir != '' and not os.path.isdir(dir):
65 file = open(filename, 'a')
67 file = open(filename, 'w')
72 def env_subst(target, source, env):
73 ''' subst variables in source by those in env, and output to target
74 source and target are scons File() objects
76 %key% (not key itself) is an indication of substitution
78 assert len(target) == 1
79 assert len(source) == 1
80 target_file = file(str(target[0]), "w")
81 source_file = file(str(source[0]), "r")
83 contents = source_file.read()
84 for k, v in env.items():
86 val = env.subst('$'+k)
87 # temporary fix for the \Resource backslash problem
88 val = val.replace('\\', '/')
89 # multi-line replacement
90 val = val.replace('\n',r'\\n\\\n')
91 contents = re.sub('@'+k+'@', val, contents)
94 target_file.write(contents + "\n")
96 #st = os.stat(str(source[0]))
97 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
100 def env_nsis(source, target, env, for_signature):
101 ''' Get nsis command line '''
102 def quoteIfSpaced(str):
104 return '"' + str + '"'
107 ret = env['NSIS'] + " /V1 "
108 if env.has_key('NSISFLAGS'):
109 for flag in env['NSISFLAGS']:
112 if env.has_key('NSISDEFINES'):
113 for d in env['NSISDEFINES']:
115 if env['NSISDEFINES'][d]:
116 ret += '=' + quoteIfSpaced(env['NSISDEFINES'][d])
119 if '-bundle.exe' in str(target[0]):
120 ret += '/DSETUPTYPE_BUNDLE=1 '
122 ret += quoteIfSpaced(str(s))
126 def env_toc(target, source, env):
127 '''Generate target from source files'''
128 # this is very tricky because we need to use installed lyx2lyx with
129 # correct lyx2lyx_version.py
130 sys.path.append(env['LYX2LYX_DEST'])
131 sys.path.append(env.Dir('$TOP_SRCDIR/lib/doc').abspath)
134 doc_toc.build_toc(str(target[0]), [file.abspath for file in source])
137 def env_cat(target, source, env):
138 '''Cat source > target. Avoid pipe to increase portability'''
139 output = open(env.File(target[0]).abspath, 'w')
141 input = open(env.File(src).abspath)
142 output.write(input.read())
147 def env_potfiles(target, source, env):
148 '''Build po/POTFILES.in'''
150 # grep -l '_(\".*\")' `find src \( -name '*.h' -o -name '*.cpp' -o -name '*.cpp.in' \) -print` | grep -v -e "src/support/Package.cpp$$" | sort | uniq
151 # is used under *nix but windows users have to do these all in python
152 target_file = open(str(target[0]), "w")
154 trans = re.compile('_\(".*"\)', re.M)
156 rel_file = relativePath(str(file), env.subst('$TOP_SRCDIR'))
157 if rel_file not in potfiles and trans.search(open(str(file)).read()):
158 potfiles.append(rel_file)
160 print >> target_file, '\n'.join(potfiles)
164 def createResFromIcon(env, icon_file, rc_file):
165 ''' create a rc file with icon, and return res file (windows only) '''
167 rc_name = env.File(rc_file).abspath
168 dir = os.path.split(rc_name)[0]
169 if not os.path.isdir(dir):
171 rc = open(rc_name, 'w')
172 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
173 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
174 'packaging', 'icons', icon_file).replace('\\', '\\\\')
176 return env.RES(rc_name)
185 def checkPkgConfig(conf, version):
186 ''' Return false if pkg_config does not exist, or is too old '''
187 conf.Message('Checking for pkg-config...')
188 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
193 def checkPackage(conf, pkg):
194 ''' check if pkg is under the control of conf '''
195 conf.Message('Checking for package %s...' % pkg)
196 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
201 def checkMkdirOneArg(conf):
202 check_mkdir_one_arg_source = """
203 #include <sys/stat.h>
209 conf.Message('Checking for the number of args for mkdir... ')
210 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
211 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
212 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
220 def checkCXXGlobalCstd(conf):
221 ''' Checking the use of std::tolower or tolower '''
222 check_global_cstd_source = '''
230 conf.Message('Checking for the use of global cstd... ')
231 ret = conf.TryLink(check_global_cstd_source, '.c')
236 def checkSelectArgType(conf):
237 ''' Adapted from autoconf '''
238 conf.Message('Checking for arg types for select... ')
239 for arg234 in ['fd_set *', 'int *', 'void *']:
240 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
241 for arg5 in ['struct timeval *', 'const struct timeval *']:
242 check_select_source = '''
243 #if HAVE_SYS_SELECT_H
244 # include <sys/select.h>
246 #if HAVE_SYS_SOCKET_H
247 # include <sys/socket.h>
249 extern int select (%s, %s, %s, %s, %s);
254 ''' % (arg1, arg234, arg234, arg234, arg5)
255 ret = conf.TryLink(check_select_source, '.c')
258 return (arg1, arg234, arg5)
259 conf.Result('no (use default)')
260 return ('int', 'int *', 'struct timeval *')
263 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
264 ''' look for boost libraries
266 lib_paths: try these paths for boost libraries
267 inc_paths: try these paths for boost headers
268 versions: supported boost versions
269 isDebug: if true, use debug libraries
271 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
272 libprefix = conf.env['LIBPREFIX']
273 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
279 for path in lib_paths:
280 conf.Log("Looking into %s\n" % path)
282 # get all the libs, then filter for the right library
283 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
284 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
286 conf.Log("Find boost libraries: %s\n" % files)
287 # runtime code includes s,g,y,d,p,n, where we should look for
288 # d,g,y for debug, s,p,n for release
292 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
295 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
296 if len(lib_files) == 0:
297 # use alternative libraries
299 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
300 if len(lib_files) > 0:
301 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
302 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
303 lib_names.append(name.split('.')[0])
304 conf.Log("Qualified libraries: %s\n" % lib_names)
306 conf.Log("No qualified library is found.\n")
308 if len(lib_names) == len(libs):
313 if len(lib_names) == 0:
314 conf.Log("No boost library is found\n")
316 conf.Log("Found boost libraries: %s\n" % lib_names)
318 return (None, None, None)
319 # check version number in boost/version.hpp
320 def isValidBoostDir(dir):
321 version_file = os.path.join(dir, 'boost', 'version.hpp')
322 if not os.path.isfile(version_file):
324 version_file_content = open(version_file).read()
325 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
326 return True in [x in version_file_content for x in version_strings]
327 # check for boost header file
328 for path in inc_paths:
329 conf.Log("Checking for inc path: %s\n" % path)
330 if isValidBoostDir(path):
333 else: # check path/boost_1_xx_x/boost
334 dirs = glob.glob(os.path.join(path, 'boost-*'))
335 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
336 conf.Log("Checing for sub directory: %s\n" % dirs[0])
342 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
343 return (lib_names, lib_path, inc_path)
346 return (None, None, None)
349 def checkCommand(conf, cmd):
350 ''' check the existence of a command
351 return full path to the command, or none
353 conf.Message('Checking for command %s...' % cmd)
355 conf.Result(res is not None)
360 ''' check the existence of nsis compiler, return the fullpath '''
361 conf.Message('Checking for nsis compiler...')
364 # If we can read the registry, get the NSIS command from it
366 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
368 val, tok = RegQueryValueEx(k,None)
369 ret = val + os.path.sep + 'makensis.exe'
370 if os.path.isfile(ret):
371 res = '"' + ret + '"'
375 pass # Couldn't find the key, just act like we can't read the registry
376 # Hope it's on the path
378 res = WhereIs('makensis.exe')
379 conf.Result(res is not None)
383 def checkLC_MESSAGES(conf):
384 ''' check the definition of LC_MESSAGES '''
385 check_LC_MESSAGES = '''
392 conf.Message('Checking for LC_MESSAGES in locale.h... ')
393 ret = conf.TryLink(check_LC_MESSAGES, '.c')
398 def checkIconvConst(conf):
399 ''' check the declaration of iconv '''
400 check_iconv_const = '''
402 // this declaration will fail when there already exists a non const char**
403 // version which returns size_t
404 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
409 conf.Message('Checking if the declaration of iconv needs const... ')
410 ret = conf.TryLink(check_iconv_const, '.c')
415 def checkSizeOfWChar(conf):
416 ''' check the size of wchar '''
417 check_sizeof_wchar = '''
418 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
424 conf.Message('Checking the size of wchar_t... ')
425 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
427 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
431 conf.Result(str(ret))
435 def checkDeclaration(conf, func, headers):
436 ''' check if a function is declared in given headers '''
442 char *p = (char *) %s;
446 conf.Message('Checking for the declaration of function %s... ' % func)
447 ret = True in [conf.TryLink(check_decl % header, '.c') for header in headers]
452 def createConfigFile(conf, config_file,
453 config_pre = '', config_post = '',
454 headers = [], functions = [], declarations = [], types = [], libs = [],
455 custom_tests = [], extra_items = []):
456 ''' create a configuration file, with options
457 config_file: which file to create
458 config_pre: first part of the config file
459 config_post: last part of the config file
460 headers: header files to check, in the form of a list of
461 ('file', 'HAVE_FILE', 'c'/'c++')
462 functions: functions to check, in the form of a list of
463 ('func', 'HAVE_func', 'include lines'/None)
464 declarations: function declarations to check, in the form of a list of
465 ('func', 'HAVE_DECL_func', header_files)
466 types: types to check, in the form of a list of
467 ('type', 'HAVE_TYPE', 'includelines'/None)
468 libs: libraries to check, in the form of a list of
469 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
470 or any of the libs exists if 'lib' is a list of libs.
471 Optionally, user can provide another key LIB_NAME, that will
472 be set to the detected lib (or None otherwise).
473 custom_tests: extra tests to perform, in the form of a list of
474 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
475 If the last two are ignored, '#define key 1' '/*#undef key */'
477 extra_items: extra configuration lines, in the form of a list of
478 ('config', 'description')
480 The result of each test, as a dictioanry of
481 res['XXX'] = True/False
482 XXX are keys defined in each argument.
484 cont = config_pre + '\n'
486 # add to this string, in appropriate format
487 def configString(lines, desc=''):
489 if lines.strip() != '':
491 text += '/* ' + desc + ' */\n'
492 text += lines + '\n\n'
496 for header in headers:
497 description = "Define to 1 if you have the <%s> header file." % header[0]
498 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
499 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
500 result[header[1]] = 1
501 cont += configString('#define %s 1' % header[1], desc = description)
503 result[header[1]] = 0
504 cont += configString('/* #undef %s */' % header[1], desc = description)
506 for func in functions:
507 description = "Define to 1 if you have the `%s' function." % func[0]
508 if conf.CheckFunc(func[0], header=func[2]):
510 cont += configString('#define %s 1' % func[1], desc = description)
513 cont += configString('/* #undef %s */' % func[1], desc = description)
514 for decl in declarations:
515 description = "Define to 1 if you have the declaration of `%s', and to 0 if you don't." % decl[0]
516 if conf.CheckDeclaration(decl[0], decl[2]):
518 cont += configString('#define %s 1' % decl[1], desc = description)
521 cont += configString('/* #undef %s */' % decl[1], desc = description)
524 description = "Define to 1 if you have the `%s' type." % t[0]
525 if conf.CheckType(t[0], includes=t[2]):
527 cont += configString('#define %s 1' % t[1], desc = description)
530 cont += configString('/* #undef %s */' % t[1], desc = description)
533 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
534 if type(lib[0]) is type(''):
538 # check if any of the lib exists
540 # if user want the name of the lib detected
542 result[lib[2]] = None
544 if conf.CheckLib(ll):
548 cont += configString('#define %s 1' % lib[1], desc = description)
551 if not result[lib[1]]:
552 cont += configString('/* #undef %s */' % lib[1], desc = description)
554 for test in custom_tests:
558 cont += configString('#define %s 1' % test[1], desc = test[2])
560 cont += configString(test[3], desc = test[2])
564 cont += configString('/* #undef %s */' % test[1], desc = test[2])
566 cont += configString(test[4], desc = test[2])
567 # extra items (no key is returned)
568 for item in extra_items:
569 cont += configString(item[0], desc = item[1])
571 cont += '\n' + config_post + '\n'
573 writeToFile(config_file, cont)
577 def installCygwinLDScript(path):
578 ''' Install i386pe.x-no-rdata '''
579 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
580 script = open(ld_script, 'w')
581 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
583 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
584 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
586 OUTPUT_FORMAT(pei-i386)
587 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
588 ENTRY(_mainCRTStartup)
591 .text __image_base__ + __section_alignment__ :
598 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
599 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
600 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
601 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
603 /* ??? Why is .gcc_exc here? */
608 /* The Cygwin32 library uses a section to avoid copying certain data
609 on fork. This used to be named ".data". The linker used
610 to include this between __data_start__ and __data_end__, but that
611 breaks building the cygwin32 dll. Instead, we name the section
612 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
613 .data BLOCK(__section_alignment__) :
622 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
623 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
624 *(.rdata_runtime_pseudo_reloc)
625 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
626 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
628 *(.data_cygwin_nocopy)
630 .rdata BLOCK(__section_alignment__) :
633 .pdata BLOCK(__section_alignment__) :
637 .bss BLOCK(__section_alignment__) :
644 .edata BLOCK(__section_alignment__) :
655 .idata BLOCK(__section_alignment__) :
657 /* This cannot currently be handled with grouped sections.
658 See pe.em:sort_sections. */
661 /* These zeroes mark the end of the import list. */
662 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
668 .CRT BLOCK(__section_alignment__) :
670 ___crt_xc_start__ = . ;
671 *(SORT(.CRT$XC*)) /* C initialization */
672 ___crt_xc_end__ = . ;
673 ___crt_xi_start__ = . ;
674 *(SORT(.CRT$XI*)) /* C++ initialization */
675 ___crt_xi_end__ = . ;
676 ___crt_xl_start__ = . ;
677 *(SORT(.CRT$XL*)) /* TLS callbacks */
678 /* ___crt_xl_end__ is defined in the TLS Directory support code */
679 ___crt_xp_start__ = . ;
680 *(SORT(.CRT$XP*)) /* Pre-termination */
681 ___crt_xp_end__ = . ;
682 ___crt_xt_start__ = . ;
683 *(SORT(.CRT$XT*)) /* Termination */
684 ___crt_xt_end__ = . ;
686 .tls BLOCK(__section_alignment__) :
694 .endjunk BLOCK(__section_alignment__) :
696 /* end is deprecated, don't use it */
701 .rsrc BLOCK(__section_alignment__) :
706 .reloc BLOCK(__section_alignment__) :
710 .stab BLOCK(__section_alignment__) (NOLOAD) :
714 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
718 /* DWARF debug sections.
719 Symbols in the DWARF debugging sections are relative to the beginning
720 of the section. Unlike other targets that fake this by putting the
721 section VMA at 0, the PE format will not allow it. */
722 /* DWARF 1.1 and DWARF 2. */
723 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
727 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
732 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
734 *(.debug_info) *(.gnu.linkonce.wi.*)
736 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
740 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
744 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
748 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
752 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
756 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
760 /* SGI/MIPS DWARF 2 extensions. */
761 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
765 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
769 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
773 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
778 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
788 def installCygwinPostinstallScript(path):
789 ''' Install lyx.sh '''
790 postinstall_script = os.path.join(path, 'lyx.sh')
791 script = open(postinstall_script, 'w')
792 script.write(r'''#!/bin/sh
794 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
795 # if it is not already there.
796 if [ -f /etc/fonts/local.conf ]; then
797 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
798 if [ $? -ne 0 ]; then
799 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
800 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
801 fc-cache /usr/share/lyx/fonts
806 return(postinstall_script)
810 # these will be used under win32
816 # does not matter if it fails on other systems
821 def __init__(self, env, logfile, longarg, info):
822 # save the spawn system
824 self.logfile = logfile
825 # clear the logfile (it may not exist)
827 # this will overwrite existing content.
828 writeToFile(logfile, info, append=False)
830 self.longarg = longarg
831 # get hold of the old spawn? (necessary?)
832 self._spawn = env['SPAWN']
835 def spawn(self, sh, escape, cmd, args, spawnenv):
837 newargs = ' '.join(map(escape, args[1:]))
838 cmdline = cmd + " " + newargs
840 # if log is not empty, write to it
841 if self.logfile != '':
842 # this tend to be slow (?) but ensure correct output
843 # Note that cmdline may be long so I do not escape it
845 # since this is not an essential operation, proceed if things go wrong here.
846 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
848 print "Warning: can not write to log file ", self.logfile
850 # if the command is not too long, use the old
851 if not self.longarg or len(cmdline) < 8000:
852 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
854 sAttrs = win32security.SECURITY_ATTRIBUTES()
855 StartupInfo = win32process.STARTUPINFO()
857 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
858 # check for any special operating system commands
861 win32file.DeleteFile(arg)
864 # otherwise execute the command.
865 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
866 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
867 exit_code = win32process.GetExitCodeProcess(hProcess)
868 win32file.CloseHandle(hProcess);
869 win32file.CloseHandle(hThread);
873 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
874 ''' This function modify env and allow logging of
875 commands to a logfile. If the argument is too long
876 a win32 spawn will be used instead of the system one
879 # create a new spwn object
880 ls = loggedSpawn(env, logfile, longarg, info)
881 # replace the old SPAWN by the new function
882 env['SPAWN'] = ls.spawn