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 WhereIs
19 def writeToFile(filename, lines, append = False):
20 " utility function: write or append lines to filename "
21 # create directory if needed
22 dir = os.path.split(filename)[0]
23 if dir != '' and not os.path.isdir(dir):
26 file = open(filename, 'a')
28 file = open(filename, 'w')
33 def env_subst(target, source, env):
34 ''' subst variables in source by those in env, and output to target
35 source and target are scons File() objects
37 %key% (not key itself) is an indication of substitution
39 assert len(target) == 1
40 assert len(source) == 1
41 target_file = file(str(target[0]), "w")
42 source_file = file(str(source[0]), "r")
44 contents = source_file.read()
45 for k, v in env.items():
47 val = env.subst('$'+k)
48 # temporary fix for the \Resource backslash problem
49 val = val.replace('\\', '/')
50 # multi-line replacement
51 val = val.replace('\n',r'\\n\\\n')
52 contents = re.sub('@'+k+'@', val, contents)
53 contents = re.sub('%'+k+'%', val, contents)
56 target_file.write(contents + "\n")
58 #st = os.stat(str(source[0]))
59 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
65 def globSource(dir, pattern, build_dir = None, exclude = [], include = []):
66 ''' glob files, in dir and use build_dir as returned path name '''
67 # exclude 'exclude+include' to avoid duplicate items in files
68 files = include + filter(lambda x: x not in exclude + include, glob.glob1(dir, pattern))
72 return ['%s/%s' % (build_dir, x) for x in files]
78 def checkPkgConfig(conf, version):
79 ''' Return false if pkg_config does not exist, or is too old '''
80 conf.Message('Checking for pkg-config...')
81 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
86 def checkPackage(conf, pkg):
87 ''' check if pkg is under the control of conf '''
88 conf.Message('Checking for package %s...' % pkg)
89 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
94 def checkMkdirOneArg(conf):
95 check_mkdir_one_arg_source = """
102 conf.Message('Checking for the number of args for mkdir... ')
103 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
104 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
105 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
113 def checkCXXGlobalCstd(conf):
114 ''' Check the use of std::tolower or tolower '''
115 check_global_cstd_source = '''
123 conf.Message('Check for the use of global cstd... ')
124 ret = conf.TryLink(check_global_cstd_source, '.c')
129 def checkSelectArgType(conf):
130 ''' Adapted from autoconf '''
131 conf.Message('Checking for arg types for select... ')
132 for arg234 in ['fd_set *', 'int *', 'void *']:
133 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
134 for arg5 in ['struct timeval *', 'const struct timeval *']:
135 check_select_source = '''
136 #if HAVE_SYS_SELECT_H
137 # include <sys/select.h>
139 #if HAVE_SYS_SOCKET_H
140 # include <sys/socket.h>
142 extern int select (%s, %s, %s, %s, %s);
147 ''' % (arg1, arg234, arg234, arg234, arg5)
148 ret = conf.TryLink(check_select_source, '.c')
151 return (arg1, arg234, arg5)
152 conf.Result('no (use default)')
153 return ('int', 'int *', 'struct timeval *')
156 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, isDebug):
157 ''' look for boost libraries
159 lib_paths: try these paths for boost libraries
160 inc_paths: try these paths for boost headers
161 isDebug: if true, use debug libraries
163 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
169 for path in lib_paths:
170 # direct form: e.g. libboost_iostreams.a
172 if False not in [os.path.isfile(os.path.join(path, 'libboost_%s.a' % lib)) for lib in libs]:
179 # get all the libs, then filter for the right library
180 files = glob.glob(os.path.join(path, 'libboost_%s-*.a' % lib))
181 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
184 files = filter(lambda x: re.search('libboost_%s-\w+-mt-d-[\d-]+' % lib, x), files)
186 files = filter(lambda x: re.search('libboost_%s-\w+-mt-[\d-]+' % lib, x), files)
188 print 'Warning: %s directory seems to have the boost libraries, but ' % path
189 print 'I can not find one that has the form lib%s-xxx-mt[-d]-x_xx_x.a' % lib
190 print 'Check your boost installation, or change select criteria in scons_util.py'
192 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
193 lib_names.append(files[0].split(os.sep)[-1][3:-2])
194 if len(lib_names) == len(libs):
200 return (None, None, None)
201 # check for boost header file
202 for path in inc_paths:
203 # check path/boost/regex.h
204 if os.path.isfile(os.path.join(path, 'boost', 'regex.h')):
207 else: # check path/boost_1_xx_x/boost
208 dirs = glob.glob(os.path.join(path, 'boost-*'))
209 if len(dirs) > 0 and os.path.isfile(os.path.join(dirs[0], 'boost', 'regex.h')):
215 return (lib_names, lib_path, inc_path)
218 return (None, None, None)
221 def checkCommand(conf, cmd):
222 ''' check the existence of a command
223 return full path to the command, or none
225 conf.Message('Checking for command %s...' % cmd)
227 conf.Result(res is not None)
231 def checkLC_MESSAGES(conf):
232 ''' check the definition of LC_MESSAGES '''
233 check_LC_MESSAGES = '''
240 conf.Message('Check for LC_MESSAGES in locale.h... ')
241 ret = conf.TryLink(check_LC_MESSAGES, '.c')
246 def checkIconvConst(conf):
247 ''' check the declaration of iconv '''
248 check_iconv_const = '''
255 #if defined(__STDC__) || defined(__cplusplus)
256 #ifndef LIBICONV_DLL_EXPORTED
257 size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
268 conf.Message('Check if the declaration of iconv needs const... ')
269 ret = conf.TryLink(check_iconv_const, '.c')
274 def createConfigFile(conf, config_file,
275 config_pre = '', config_post = '',
276 headers = [], functions = [], types = [], libs = [],
277 custom_tests = [], extra_items = []):
278 ''' create a configuration file, with options
279 config_file: which file to create
280 config_pre: first part of the config file
281 config_post: last part of the config file
282 headers: header files to check, in the form of a list of
283 ('file', 'HAVE_FILE', 'c'/'c++')
284 functions: functions to check, in the form of a list of
285 ('func', 'HAVE_func', 'include lines'/None)
286 types: types to check, in the form of a list of
287 ('type', 'HAVE_TYPE', 'includelines'/None)
288 libs: libraries to check, in the form of a list of
289 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
290 or any of the libs exists if 'lib' is a list of libs.
291 Optionally, user can provide another key LIB_NAME, that will
292 be set to the detected lib (or None otherwise).
293 custom_tests: extra tests to perform, in the form of a list of
294 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
295 If the last two are ignored, '#define key 1' '/*#undef key */'
297 extra_items: extra configuration lines, in the form of a list of
298 ('config', 'description')
300 The result of each test, as a dictioanry of
301 res['XXX'] = True/False
302 XXX are keys defined in each argument.
304 cont = config_pre + '\n'
306 # add to this string, in appropriate format
307 def configString(lines, desc=''):
309 if lines.strip() != '':
311 text += '/* ' + desc + ' */\n'
312 text += lines + '\n\n'
316 for header in headers:
317 description = "Define to 1 if you have the <%s> header file." % header[0]
318 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
319 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
320 result[header[1]] = True
321 cont += configString('#define %s 1' % header[1], desc = description)
323 result[header[1]] = False
324 cont += configString('/* #undef %s */' % header[1], desc = description)
326 for func in functions:
327 description = "Define to 1 if you have the `%s' function." % func[0]
328 if conf.CheckFunc(func[0], header=func[2]):
329 result[func[1]] = True
330 cont += configString('#define %s 1' % func[1], desc = description)
332 result[func[1]] = False
333 cont += configString('/* #undef %s */' % func[1], desc = description)
336 description = "Define to 1 if you have the `%s' type." % t[0]
337 if conf.CheckType(t[0], includes=t[2]):
339 cont += configString('#define %s 1' % t[1], desc = description)
342 cont += configString('/* #undef %s */' % t[1], desc = description)
345 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
346 if type(lib[0]) is type(''):
350 # check if any of the lib exists
351 result[lib[1]] = False
352 # if user want the name of the lib detected
354 result[lib[2]] = None
356 if conf.CheckLib(ll):
357 result[lib[1]] = True
360 cont += configString('#define %s 1' % lib[1], desc = description)
363 if not result[lib[1]]:
364 cont += configString('/* #undef %s */' % lib[1], desc = description)
366 for test in custom_tests:
368 result[test[1]] = True
370 cont += configString('#define %s 1' % test[1], desc = test[2])
372 cont += configString(test[3], desc = test[2])
374 result[test[1]] = False
376 cont += configString('/* #undef %s */' % test[1], desc = test[2])
378 cont += configString(test[4], desc = test[2])
379 # extra items (no key is returned)
380 for item in extra_items:
381 cont += configString(item[0], desc = item[1])
383 cont += '\n' + config_post + '\n'
385 writeToFile(config_file, cont)
389 def installCygwinLDScript(path):
390 ''' Install i386pe.x-no-rdata '''
391 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
392 script = open(ld_script, 'w')
393 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
395 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
396 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
398 OUTPUT_FORMAT(pei-i386)
399 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
400 ENTRY(_mainCRTStartup)
403 .text __image_base__ + __section_alignment__ :
410 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
411 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
412 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
413 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
415 /* ??? Why is .gcc_exc here? */
420 /* The Cygwin32 library uses a section to avoid copying certain data
421 on fork. This used to be named ".data". The linker used
422 to include this between __data_start__ and __data_end__, but that
423 breaks building the cygwin32 dll. Instead, we name the section
424 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
425 .data BLOCK(__section_alignment__) :
434 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
435 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
436 *(.rdata_runtime_pseudo_reloc)
437 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
438 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
440 *(.data_cygwin_nocopy)
442 .rdata BLOCK(__section_alignment__) :
445 .pdata BLOCK(__section_alignment__) :
449 .bss BLOCK(__section_alignment__) :
456 .edata BLOCK(__section_alignment__) :
467 .idata BLOCK(__section_alignment__) :
469 /* This cannot currently be handled with grouped sections.
470 See pe.em:sort_sections. */
473 /* These zeroes mark the end of the import list. */
474 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
480 .CRT BLOCK(__section_alignment__) :
482 ___crt_xc_start__ = . ;
483 *(SORT(.CRT$XC*)) /* C initialization */
484 ___crt_xc_end__ = . ;
485 ___crt_xi_start__ = . ;
486 *(SORT(.CRT$XI*)) /* C++ initialization */
487 ___crt_xi_end__ = . ;
488 ___crt_xl_start__ = . ;
489 *(SORT(.CRT$XL*)) /* TLS callbacks */
490 /* ___crt_xl_end__ is defined in the TLS Directory support code */
491 ___crt_xp_start__ = . ;
492 *(SORT(.CRT$XP*)) /* Pre-termination */
493 ___crt_xp_end__ = . ;
494 ___crt_xt_start__ = . ;
495 *(SORT(.CRT$XT*)) /* Termination */
496 ___crt_xt_end__ = . ;
498 .tls BLOCK(__section_alignment__) :
506 .endjunk BLOCK(__section_alignment__) :
508 /* end is deprecated, don't use it */
513 .rsrc BLOCK(__section_alignment__) :
518 .reloc BLOCK(__section_alignment__) :
522 .stab BLOCK(__section_alignment__) (NOLOAD) :
526 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
530 /* DWARF debug sections.
531 Symbols in the DWARF debugging sections are relative to the beginning
532 of the section. Unlike other targets that fake this by putting the
533 section VMA at 0, the PE format will not allow it. */
534 /* DWARF 1.1 and DWARF 2. */
535 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
539 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
544 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
546 *(.debug_info) *(.gnu.linkonce.wi.*)
548 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
552 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
556 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
560 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
564 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
568 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
572 /* SGI/MIPS DWARF 2 extensions. */
573 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
577 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
581 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
585 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
590 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
601 # these will be used under win32
607 # does not matter if it fails on other systems
612 def __init__(self, env, logfile, longarg, info):
613 # save the spawn system
615 self.logfile = logfile
616 # clear the logfile (it may not exist)
618 # this will overwrite existing content.
619 writeToFile(logfile, info, append=False)
621 self.longarg = longarg
622 # get hold of the old spawn? (necessary?)
623 self._spawn = env['SPAWN']
626 def spawn(self, sh, escape, cmd, args, spawnenv):
628 newargs = ' '.join(map(escape, args[1:]))
629 cmdline = cmd + " " + newargs
631 # if log is not empty, write to it
632 if self.logfile != '':
633 # this tend to be slow (?) but ensure correct output
634 # Note that cmdline may be long so I do not escape it
636 # since this is not an essential operation, proceed if things go wrong here.
637 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
639 print "Warning: can not write to log file ", self.logfile
641 # if the command is not too long, use the old
642 if not self.longarg or len(cmdline) < 8000:
643 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
645 sAttrs = win32security.SECURITY_ATTRIBUTES()
646 StartupInfo = win32process.STARTUPINFO()
648 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
649 # check for any special operating system commands
652 win32file.DeleteFile(arg)
655 # otherwise execute the command.
656 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
657 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
658 exit_code = win32process.GetExitCodeProcess(hProcess)
659 win32file.CloseHandle(hProcess);
660 win32file.CloseHandle(hThread);
664 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
665 ''' This function modify env and allow logging of
666 commands to a logfile. If the argument is too long
667 a win32 spawn will be used instead of the system one
670 # create a new spwn object
671 ls = loggedSpawn(env, logfile, longarg, info)
672 # replace the old SPAWN by the new function
673 env['SPAWN'] = ls.spawn