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 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()
37 def writeToFile(filename, lines, append = False):
38 " utility function: write or append lines to filename "
39 # create directory if needed
40 dir = os.path.split(filename)[0]
41 if dir != '' and not os.path.isdir(dir):
44 file = open(filename, 'a')
46 file = open(filename, 'w')
51 def env_subst(target, source, env):
52 ''' subst variables in source by those in env, and output to target
53 source and target are scons File() objects
55 %key% (not key itself) is an indication of substitution
57 assert len(target) == 1
58 assert len(source) == 1
59 target_file = file(str(target[0]), "w")
60 source_file = file(str(source[0]), "r")
62 contents = source_file.read()
63 for k, v in env.items():
65 val = env.subst('$'+k)
66 # temporary fix for the \Resource backslash problem
67 val = val.replace('\\', '/')
68 # multi-line replacement
69 val = val.replace('\n',r'\\n\\\n')
70 contents = re.sub('@'+k+'@', val, contents)
71 contents = re.sub('%'+k+'%', val, contents)
74 target_file.write(contents + "\n")
76 #st = os.stat(str(source[0]))
77 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
83 def checkPkgConfig(conf, version):
84 ''' Return false if pkg_config does not exist, or is too old '''
85 conf.Message('Checking for pkg-config...')
86 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
91 def checkPackage(conf, pkg):
92 ''' check if pkg is under the control of conf '''
93 conf.Message('Checking for package %s...' % pkg)
94 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
99 def checkMkdirOneArg(conf):
100 check_mkdir_one_arg_source = """
101 #include <sys/stat.h>
107 conf.Message('Checking for the number of args for mkdir... ')
108 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
109 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
110 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
118 def checkCXXGlobalCstd(conf):
119 ''' Check the use of std::tolower or tolower '''
120 check_global_cstd_source = '''
128 conf.Message('Check for the use of global cstd... ')
129 ret = conf.TryLink(check_global_cstd_source, '.c')
134 def checkSelectArgType(conf):
135 ''' Adapted from autoconf '''
136 conf.Message('Checking for arg types for select... ')
137 for arg234 in ['fd_set *', 'int *', 'void *']:
138 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
139 for arg5 in ['struct timeval *', 'const struct timeval *']:
140 check_select_source = '''
141 #if HAVE_SYS_SELECT_H
142 # include <sys/select.h>
144 #if HAVE_SYS_SOCKET_H
145 # include <sys/socket.h>
147 extern int select (%s, %s, %s, %s, %s);
152 ''' % (arg1, arg234, arg234, arg234, arg5)
153 ret = conf.TryLink(check_select_source, '.c')
156 return (arg1, arg234, arg5)
157 conf.Result('no (use default)')
158 return ('int', 'int *', 'struct timeval *')
161 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, version, isDebug):
162 ''' look for boost libraries
164 lib_paths: try these paths for boost libraries
165 inc_paths: try these paths for boost headers
166 version: required boost version
167 isDebug: if true, use debug libraries
169 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
175 for path in lib_paths:
176 # direct form: e.g. libboost_iostreams.a
178 if False not in [os.path.isfile(os.path.join(path, 'libboost_%s.a' % lib)) for lib in libs]:
185 # get all the libs, then filter for the right library
186 files = glob.glob(os.path.join(path, 'libboost_%s-*.a' % lib))
187 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
189 # runtime code includes s,g,y,d,p,n, where we should look for
190 # d,g,y for debug, s,p,n for release
192 lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-[^spn]+-%s.a' % (lib, version), x), files)
194 lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-([^dgy]+-)*%s.a' % (lib, version), x), files)
195 if len(lib_files) == 0:
196 print 'Warning: Can not find an appropriate boost library in %s.' % path
197 lib_files = filter(lambda x: re.search('libboost_%s-[\w-]+%s.a' % (lib, version), x), files)
198 if len(lib_files) > 0:
199 print 'Use library %s' % lib_files[0]
200 if len(lib_files) > 0:
201 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
202 lib_names.append(lib_files[0].split(os.sep)[-1][3:-2])
203 if len(lib_names) == len(libs):
209 return (None, None, None)
210 # check version number in boost/version.hpp
211 def isValidBoostDir(dir):
212 file = os.path.join(dir, 'boost', 'version.hpp')
213 version_string = '#define BOOST_LIB_VERSION "%s"' % version
214 return os.path.isfile(file) and version_string in open(file).read()
215 # check for boost header file
216 for path in inc_paths:
217 if isValidBoostDir(path):
220 else: # check path/boost_1_xx_x/boost
221 dirs = glob.glob(os.path.join(path, 'boost-*'))
222 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
228 return (lib_names, lib_path, inc_path)
231 return (None, None, None)
234 def checkCommand(conf, cmd):
235 ''' check the existence of a command
236 return full path to the command, or none
238 conf.Message('Checking for command %s...' % cmd)
240 conf.Result(res is not None)
244 def checkLC_MESSAGES(conf):
245 ''' check the definition of LC_MESSAGES '''
246 check_LC_MESSAGES = '''
253 conf.Message('Check for LC_MESSAGES in locale.h... ')
254 ret = conf.TryLink(check_LC_MESSAGES, '.c')
259 def checkIconvConst(conf):
260 ''' check the declaration of iconv '''
261 check_iconv_const = '''
263 // this declaration will fail when there already exists a non const char**
264 // version which returns size_t
265 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
270 conf.Message('Check if the declaration of iconv needs const... ')
271 ret = conf.TryLink(check_iconv_const, '.c')
276 def checkSizeOfWChar(conf):
277 ''' check the size of wchar '''
278 check_sizeof_wchar = '''
279 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
285 conf.Message('Check the size of wchar_t... ')
286 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
288 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
292 conf.Result(str(ret))
296 def createConfigFile(conf, config_file,
297 config_pre = '', config_post = '',
298 headers = [], functions = [], types = [], libs = [],
299 custom_tests = [], extra_items = []):
300 ''' create a configuration file, with options
301 config_file: which file to create
302 config_pre: first part of the config file
303 config_post: last part of the config file
304 headers: header files to check, in the form of a list of
305 ('file', 'HAVE_FILE', 'c'/'c++')
306 functions: functions to check, in the form of a list of
307 ('func', 'HAVE_func', 'include lines'/None)
308 types: types to check, in the form of a list of
309 ('type', 'HAVE_TYPE', 'includelines'/None)
310 libs: libraries to check, in the form of a list of
311 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
312 or any of the libs exists if 'lib' is a list of libs.
313 Optionally, user can provide another key LIB_NAME, that will
314 be set to the detected lib (or None otherwise).
315 custom_tests: extra tests to perform, in the form of a list of
316 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
317 If the last two are ignored, '#define key 1' '/*#undef key */'
319 extra_items: extra configuration lines, in the form of a list of
320 ('config', 'description')
322 The result of each test, as a dictioanry of
323 res['XXX'] = True/False
324 XXX are keys defined in each argument.
326 cont = config_pre + '\n'
328 # add to this string, in appropriate format
329 def configString(lines, desc=''):
331 if lines.strip() != '':
333 text += '/* ' + desc + ' */\n'
334 text += lines + '\n\n'
338 for header in headers:
339 description = "Define to 1 if you have the <%s> header file." % header[0]
340 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
341 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
342 result[header[1]] = True
343 cont += configString('#define %s 1' % header[1], desc = description)
345 result[header[1]] = False
346 cont += configString('/* #undef %s */' % header[1], desc = description)
348 for func in functions:
349 description = "Define to 1 if you have the `%s' function." % func[0]
350 if conf.CheckFunc(func[0], header=func[2]):
351 result[func[1]] = True
352 cont += configString('#define %s 1' % func[1], desc = description)
354 result[func[1]] = False
355 cont += configString('/* #undef %s */' % func[1], desc = description)
358 description = "Define to 1 if you have the `%s' type." % t[0]
359 if conf.CheckType(t[0], includes=t[2]):
361 cont += configString('#define %s 1' % t[1], desc = description)
364 cont += configString('/* #undef %s */' % t[1], desc = description)
367 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
368 if type(lib[0]) is type(''):
372 # check if any of the lib exists
373 result[lib[1]] = False
374 # if user want the name of the lib detected
376 result[lib[2]] = None
378 if conf.CheckLib(ll):
379 result[lib[1]] = True
382 cont += configString('#define %s 1' % lib[1], desc = description)
385 if not result[lib[1]]:
386 cont += configString('/* #undef %s */' % lib[1], desc = description)
388 for test in custom_tests:
390 result[test[1]] = True
392 cont += configString('#define %s 1' % test[1], desc = test[2])
394 cont += configString(test[3], desc = test[2])
396 result[test[1]] = False
398 cont += configString('/* #undef %s */' % test[1], desc = test[2])
400 cont += configString(test[4], desc = test[2])
401 # extra items (no key is returned)
402 for item in extra_items:
403 cont += configString(item[0], desc = item[1])
405 cont += '\n' + config_post + '\n'
407 writeToFile(config_file, cont)
411 def installCygwinLDScript(path):
412 ''' Install i386pe.x-no-rdata '''
413 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
414 script = open(ld_script, 'w')
415 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
417 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
418 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
420 OUTPUT_FORMAT(pei-i386)
421 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
422 ENTRY(_mainCRTStartup)
425 .text __image_base__ + __section_alignment__ :
432 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
433 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
434 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
435 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
437 /* ??? Why is .gcc_exc here? */
442 /* The Cygwin32 library uses a section to avoid copying certain data
443 on fork. This used to be named ".data". The linker used
444 to include this between __data_start__ and __data_end__, but that
445 breaks building the cygwin32 dll. Instead, we name the section
446 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
447 .data BLOCK(__section_alignment__) :
456 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
457 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
458 *(.rdata_runtime_pseudo_reloc)
459 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
460 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
462 *(.data_cygwin_nocopy)
464 .rdata BLOCK(__section_alignment__) :
467 .pdata BLOCK(__section_alignment__) :
471 .bss BLOCK(__section_alignment__) :
478 .edata BLOCK(__section_alignment__) :
489 .idata BLOCK(__section_alignment__) :
491 /* This cannot currently be handled with grouped sections.
492 See pe.em:sort_sections. */
495 /* These zeroes mark the end of the import list. */
496 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
502 .CRT BLOCK(__section_alignment__) :
504 ___crt_xc_start__ = . ;
505 *(SORT(.CRT$XC*)) /* C initialization */
506 ___crt_xc_end__ = . ;
507 ___crt_xi_start__ = . ;
508 *(SORT(.CRT$XI*)) /* C++ initialization */
509 ___crt_xi_end__ = . ;
510 ___crt_xl_start__ = . ;
511 *(SORT(.CRT$XL*)) /* TLS callbacks */
512 /* ___crt_xl_end__ is defined in the TLS Directory support code */
513 ___crt_xp_start__ = . ;
514 *(SORT(.CRT$XP*)) /* Pre-termination */
515 ___crt_xp_end__ = . ;
516 ___crt_xt_start__ = . ;
517 *(SORT(.CRT$XT*)) /* Termination */
518 ___crt_xt_end__ = . ;
520 .tls BLOCK(__section_alignment__) :
528 .endjunk BLOCK(__section_alignment__) :
530 /* end is deprecated, don't use it */
535 .rsrc BLOCK(__section_alignment__) :
540 .reloc BLOCK(__section_alignment__) :
544 .stab BLOCK(__section_alignment__) (NOLOAD) :
548 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
552 /* DWARF debug sections.
553 Symbols in the DWARF debugging sections are relative to the beginning
554 of the section. Unlike other targets that fake this by putting the
555 section VMA at 0, the PE format will not allow it. */
556 /* DWARF 1.1 and DWARF 2. */
557 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
561 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
566 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
568 *(.debug_info) *(.gnu.linkonce.wi.*)
570 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
574 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
578 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
582 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
586 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
590 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
594 /* SGI/MIPS DWARF 2 extensions. */
595 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
599 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
603 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
607 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
612 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
622 def installCygwinPostinstallScript(path):
623 ''' Install lyx.sh '''
624 postinstall_script = os.path.join(path, 'lyx.sh')
625 script = open(postinstall_script, 'w')
626 script.write('''#!/bin/sh
628 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
629 # if it is not already there.
630 if [ -f /etc/fonts/local.conf ]; then
631 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
632 if [ $? -ne 0 ]; then
633 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
634 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
635 fc-cache /usr/share/lyx/fonts
640 return(postinstall_script)
644 # these will be used under win32
650 # does not matter if it fails on other systems
655 def __init__(self, env, logfile, longarg, info):
656 # save the spawn system
658 self.logfile = logfile
659 # clear the logfile (it may not exist)
661 # this will overwrite existing content.
662 writeToFile(logfile, info, append=False)
664 self.longarg = longarg
665 # get hold of the old spawn? (necessary?)
666 self._spawn = env['SPAWN']
669 def spawn(self, sh, escape, cmd, args, spawnenv):
671 newargs = ' '.join(map(escape, args[1:]))
672 cmdline = cmd + " " + newargs
674 # if log is not empty, write to it
675 if self.logfile != '':
676 # this tend to be slow (?) but ensure correct output
677 # Note that cmdline may be long so I do not escape it
679 # since this is not an essential operation, proceed if things go wrong here.
680 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
682 print "Warning: can not write to log file ", self.logfile
684 # if the command is not too long, use the old
685 if not self.longarg or len(cmdline) < 8000:
686 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
688 sAttrs = win32security.SECURITY_ATTRIBUTES()
689 StartupInfo = win32process.STARTUPINFO()
691 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
692 # check for any special operating system commands
695 win32file.DeleteFile(arg)
698 # otherwise execute the command.
699 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
700 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
701 exit_code = win32process.GetExitCodeProcess(hProcess)
702 win32file.CloseHandle(hProcess);
703 win32file.CloseHandle(hThread);
707 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
708 ''' This function modify env and allow logging of
709 commands to a logfile. If the argument is too long
710 a win32 spawn will be used instead of the system one
713 # create a new spwn object
714 ls = loggedSpawn(env, logfile, longarg, info)
715 # replace the old SPAWN by the new function
716 env['SPAWN'] = ls.spawn