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, version, 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 version: required boost version
162 isDebug: if true, use debug libraries
164 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
170 for path in lib_paths:
171 # direct form: e.g. libboost_iostreams.a
173 if False not in [os.path.isfile(os.path.join(path, 'libboost_%s.a' % lib)) for lib in libs]:
180 # get all the libs, then filter for the right library
181 files = glob.glob(os.path.join(path, 'libboost_%s-*.a' % lib))
182 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
184 # runtime code includes s,g,y,d,p,n, where we should look for
185 # d,g,y for debug, s,p,n for release
187 lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-[^spn]+-%s.a' % (lib, version), x), files)
189 lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-([^dgy]+-)*%s.a' % (lib, version), x), files)
190 if len(lib_files) == 0:
191 print 'Warning: Can not find an appropriate boost library in %s.' % path
192 lib_files = filter(lambda x: re.search('libboost_%s-[\w-]+%s.a' % (lib, version), x), files)
193 if len(lib_files) > 0:
194 print 'Use library ' % lib_files[0]
195 if len(lib_files) > 0:
196 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
197 lib_names.append(lib_files[0].split(os.sep)[-1][3:-2])
198 if len(lib_names) == len(libs):
204 return (None, None, None)
205 # check version number in boost/version.hpp
206 def isValidBoostDir(dir):
207 file = os.path.join(dir, 'boost', 'version.hpp')
208 version_string = '#define BOOST_LIB_VERSION "%s"' % version
209 return os.path.isfile(file) and version_string in open(file).read()
210 # check for boost header file
211 for path in inc_paths:
212 if isValidBoostDir(path):
215 else: # check path/boost_1_xx_x/boost
216 dirs = glob.glob(os.path.join(path, 'boost-*'))
217 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
223 return (lib_names, lib_path, inc_path)
226 return (None, None, None)
229 def checkCommand(conf, cmd):
230 ''' check the existence of a command
231 return full path to the command, or none
233 conf.Message('Checking for command %s...' % cmd)
235 conf.Result(res is not None)
239 def checkLC_MESSAGES(conf):
240 ''' check the definition of LC_MESSAGES '''
241 check_LC_MESSAGES = '''
248 conf.Message('Check for LC_MESSAGES in locale.h... ')
249 ret = conf.TryLink(check_LC_MESSAGES, '.c')
254 def checkIconvConst(conf):
255 ''' check the declaration of iconv '''
256 check_iconv_const = '''
263 #if defined(__STDC__) || defined(__cplusplus)
264 #ifndef LIBICONV_DLL_EXPORTED
265 size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
276 conf.Message('Check if the declaration of iconv needs const... ')
277 ret = conf.TryLink(check_iconv_const, '.c')
282 def createConfigFile(conf, config_file,
283 config_pre = '', config_post = '',
284 headers = [], functions = [], types = [], libs = [],
285 custom_tests = [], extra_items = []):
286 ''' create a configuration file, with options
287 config_file: which file to create
288 config_pre: first part of the config file
289 config_post: last part of the config file
290 headers: header files to check, in the form of a list of
291 ('file', 'HAVE_FILE', 'c'/'c++')
292 functions: functions to check, in the form of a list of
293 ('func', 'HAVE_func', 'include lines'/None)
294 types: types to check, in the form of a list of
295 ('type', 'HAVE_TYPE', 'includelines'/None)
296 libs: libraries to check, in the form of a list of
297 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
298 or any of the libs exists if 'lib' is a list of libs.
299 Optionally, user can provide another key LIB_NAME, that will
300 be set to the detected lib (or None otherwise).
301 custom_tests: extra tests to perform, in the form of a list of
302 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
303 If the last two are ignored, '#define key 1' '/*#undef key */'
305 extra_items: extra configuration lines, in the form of a list of
306 ('config', 'description')
308 The result of each test, as a dictioanry of
309 res['XXX'] = True/False
310 XXX are keys defined in each argument.
312 cont = config_pre + '\n'
314 # add to this string, in appropriate format
315 def configString(lines, desc=''):
317 if lines.strip() != '':
319 text += '/* ' + desc + ' */\n'
320 text += lines + '\n\n'
324 for header in headers:
325 description = "Define to 1 if you have the <%s> header file." % header[0]
326 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
327 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
328 result[header[1]] = True
329 cont += configString('#define %s 1' % header[1], desc = description)
331 result[header[1]] = False
332 cont += configString('/* #undef %s */' % header[1], desc = description)
334 for func in functions:
335 description = "Define to 1 if you have the `%s' function." % func[0]
336 if conf.CheckFunc(func[0], header=func[2]):
337 result[func[1]] = True
338 cont += configString('#define %s 1' % func[1], desc = description)
340 result[func[1]] = False
341 cont += configString('/* #undef %s */' % func[1], desc = description)
344 description = "Define to 1 if you have the `%s' type." % t[0]
345 if conf.CheckType(t[0], includes=t[2]):
347 cont += configString('#define %s 1' % t[1], desc = description)
350 cont += configString('/* #undef %s */' % t[1], desc = description)
353 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
354 if type(lib[0]) is type(''):
358 # check if any of the lib exists
359 result[lib[1]] = False
360 # if user want the name of the lib detected
362 result[lib[2]] = None
364 if conf.CheckLib(ll):
365 result[lib[1]] = True
368 cont += configString('#define %s 1' % lib[1], desc = description)
371 if not result[lib[1]]:
372 cont += configString('/* #undef %s */' % lib[1], desc = description)
374 for test in custom_tests:
376 result[test[1]] = True
378 cont += configString('#define %s 1' % test[1], desc = test[2])
380 cont += configString(test[3], desc = test[2])
382 result[test[1]] = False
384 cont += configString('/* #undef %s */' % test[1], desc = test[2])
386 cont += configString(test[4], desc = test[2])
387 # extra items (no key is returned)
388 for item in extra_items:
389 cont += configString(item[0], desc = item[1])
391 cont += '\n' + config_post + '\n'
393 writeToFile(config_file, cont)
397 def installCygwinLDScript(path):
398 ''' Install i386pe.x-no-rdata '''
399 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
400 script = open(ld_script, 'w')
401 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
403 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
404 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
406 OUTPUT_FORMAT(pei-i386)
407 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
408 ENTRY(_mainCRTStartup)
411 .text __image_base__ + __section_alignment__ :
418 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
419 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
420 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
421 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
423 /* ??? Why is .gcc_exc here? */
428 /* The Cygwin32 library uses a section to avoid copying certain data
429 on fork. This used to be named ".data". The linker used
430 to include this between __data_start__ and __data_end__, but that
431 breaks building the cygwin32 dll. Instead, we name the section
432 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
433 .data BLOCK(__section_alignment__) :
442 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
443 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
444 *(.rdata_runtime_pseudo_reloc)
445 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
446 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
448 *(.data_cygwin_nocopy)
450 .rdata BLOCK(__section_alignment__) :
453 .pdata BLOCK(__section_alignment__) :
457 .bss BLOCK(__section_alignment__) :
464 .edata BLOCK(__section_alignment__) :
475 .idata BLOCK(__section_alignment__) :
477 /* This cannot currently be handled with grouped sections.
478 See pe.em:sort_sections. */
481 /* These zeroes mark the end of the import list. */
482 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
488 .CRT BLOCK(__section_alignment__) :
490 ___crt_xc_start__ = . ;
491 *(SORT(.CRT$XC*)) /* C initialization */
492 ___crt_xc_end__ = . ;
493 ___crt_xi_start__ = . ;
494 *(SORT(.CRT$XI*)) /* C++ initialization */
495 ___crt_xi_end__ = . ;
496 ___crt_xl_start__ = . ;
497 *(SORT(.CRT$XL*)) /* TLS callbacks */
498 /* ___crt_xl_end__ is defined in the TLS Directory support code */
499 ___crt_xp_start__ = . ;
500 *(SORT(.CRT$XP*)) /* Pre-termination */
501 ___crt_xp_end__ = . ;
502 ___crt_xt_start__ = . ;
503 *(SORT(.CRT$XT*)) /* Termination */
504 ___crt_xt_end__ = . ;
506 .tls BLOCK(__section_alignment__) :
514 .endjunk BLOCK(__section_alignment__) :
516 /* end is deprecated, don't use it */
521 .rsrc BLOCK(__section_alignment__) :
526 .reloc BLOCK(__section_alignment__) :
530 .stab BLOCK(__section_alignment__) (NOLOAD) :
534 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
538 /* DWARF debug sections.
539 Symbols in the DWARF debugging sections are relative to the beginning
540 of the section. Unlike other targets that fake this by putting the
541 section VMA at 0, the PE format will not allow it. */
542 /* DWARF 1.1 and DWARF 2. */
543 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
547 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
552 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
554 *(.debug_info) *(.gnu.linkonce.wi.*)
556 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
560 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
564 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
568 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
572 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
576 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
580 /* SGI/MIPS DWARF 2 extensions. */
581 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
585 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
589 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
593 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
598 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
609 # these will be used under win32
615 # does not matter if it fails on other systems
620 def __init__(self, env, logfile, longarg, info):
621 # save the spawn system
623 self.logfile = logfile
624 # clear the logfile (it may not exist)
626 # this will overwrite existing content.
627 writeToFile(logfile, info, append=False)
629 self.longarg = longarg
630 # get hold of the old spawn? (necessary?)
631 self._spawn = env['SPAWN']
634 def spawn(self, sh, escape, cmd, args, spawnenv):
636 newargs = ' '.join(map(escape, args[1:]))
637 cmdline = cmd + " " + newargs
639 # if log is not empty, write to it
640 if self.logfile != '':
641 # this tend to be slow (?) but ensure correct output
642 # Note that cmdline may be long so I do not escape it
644 # since this is not an essential operation, proceed if things go wrong here.
645 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
647 print "Warning: can not write to log file ", self.logfile
649 # if the command is not too long, use the old
650 if not self.longarg or len(cmdline) < 8000:
651 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
653 sAttrs = win32security.SECURITY_ATTRIBUTES()
654 StartupInfo = win32process.STARTUPINFO()
656 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
657 # check for any special operating system commands
660 win32file.DeleteFile(arg)
663 # otherwise execute the command.
664 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
665 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
666 exit_code = win32process.GetExitCodeProcess(hProcess)
667 win32file.CloseHandle(hProcess);
668 win32file.CloseHandle(hThread);
672 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
673 ''' This function modify env and allow logging of
674 commands to a logfile. If the argument is too long
675 a win32 spawn will be used instead of the system one
678 # create a new spwn object
679 ls = loggedSpawn(env, logfile, longarg, info)
680 # replace the old SPAWN by the new function
681 env['SPAWN'] = ls.spawn