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, versions, 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 versions: supported boost versions
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
194 lib_files += filter(lambda x: re.search('libboost_%s-\w+-mt-[^spn]+-%s.a' % (lib, ver), x), files)
197 lib_files += filter(lambda x: re.search('libboost_%s-\w+-mt-([^dgy]+-)*%s.a' % (lib, ver), x), files)
198 if len(lib_files) == 0:
200 print 'Failed to find the debug version of boost', ' or '.join(versions)
202 print 'Failed to find the release version of boost', ' or '.join(versions)
203 # use alternative libraries
205 lib_files += filter(lambda x: re.search('libboost_%s-[\w-]+%s.a' % (lib, ver), x), files)
206 if len(lib_files) > 0:
207 print 'Use library %s' % lib_files[0]
208 if len(lib_files) > 0:
209 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
210 lib_names.append(lib_files[0].split(os.sep)[-1][3:-2])
213 if len(lib_names) == len(libs):
219 return (None, None, None)
220 # check version number in boost/version.hpp
221 def isValidBoostDir(dir):
222 version_file = os.path.join(dir, 'boost', 'version.hpp')
223 if not os.path.isfile(version_file):
225 version_file_content = open(version_file).read()
226 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
227 return True in [x in version_file_content for x in version_strings]
228 # check for boost header file
229 for path in inc_paths:
230 if isValidBoostDir(path):
233 else: # check path/boost_1_xx_x/boost
234 dirs = glob.glob(os.path.join(path, 'boost-*'))
235 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
241 print 'Using boost libraries', ', '.join(lib_names)
242 return (lib_names, lib_path, inc_path)
245 return (None, None, None)
248 def checkCommand(conf, cmd):
249 ''' check the existence of a command
250 return full path to the command, or none
252 conf.Message('Checking for command %s...' % cmd)
254 conf.Result(res is not None)
258 def checkLC_MESSAGES(conf):
259 ''' check the definition of LC_MESSAGES '''
260 check_LC_MESSAGES = '''
267 conf.Message('Check for LC_MESSAGES in locale.h... ')
268 ret = conf.TryLink(check_LC_MESSAGES, '.c')
273 def checkIconvConst(conf):
274 ''' check the declaration of iconv '''
275 check_iconv_const = '''
277 // this declaration will fail when there already exists a non const char**
278 // version which returns size_t
279 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
284 conf.Message('Check if the declaration of iconv needs const... ')
285 ret = conf.TryLink(check_iconv_const, '.c')
290 def checkSizeOfWChar(conf):
291 ''' check the size of wchar '''
292 check_sizeof_wchar = '''
293 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
299 conf.Message('Check the size of wchar_t... ')
300 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
302 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
306 conf.Result(str(ret))
310 def createConfigFile(conf, config_file,
311 config_pre = '', config_post = '',
312 headers = [], functions = [], types = [], libs = [],
313 custom_tests = [], extra_items = []):
314 ''' create a configuration file, with options
315 config_file: which file to create
316 config_pre: first part of the config file
317 config_post: last part of the config file
318 headers: header files to check, in the form of a list of
319 ('file', 'HAVE_FILE', 'c'/'c++')
320 functions: functions to check, in the form of a list of
321 ('func', 'HAVE_func', 'include lines'/None)
322 types: types to check, in the form of a list of
323 ('type', 'HAVE_TYPE', 'includelines'/None)
324 libs: libraries to check, in the form of a list of
325 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
326 or any of the libs exists if 'lib' is a list of libs.
327 Optionally, user can provide another key LIB_NAME, that will
328 be set to the detected lib (or None otherwise).
329 custom_tests: extra tests to perform, in the form of a list of
330 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
331 If the last two are ignored, '#define key 1' '/*#undef key */'
333 extra_items: extra configuration lines, in the form of a list of
334 ('config', 'description')
336 The result of each test, as a dictioanry of
337 res['XXX'] = True/False
338 XXX are keys defined in each argument.
340 cont = config_pre + '\n'
342 # add to this string, in appropriate format
343 def configString(lines, desc=''):
345 if lines.strip() != '':
347 text += '/* ' + desc + ' */\n'
348 text += lines + '\n\n'
352 for header in headers:
353 description = "Define to 1 if you have the <%s> header file." % header[0]
354 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
355 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
356 result[header[1]] = 1
357 cont += configString('#define %s 1' % header[1], desc = description)
359 result[header[1]] = 0
360 cont += configString('/* #undef %s */' % header[1], desc = description)
362 for func in functions:
363 description = "Define to 1 if you have the `%s' function." % func[0]
364 if conf.CheckFunc(func[0], header=func[2]):
366 cont += configString('#define %s 1' % func[1], desc = description)
369 cont += configString('/* #undef %s */' % func[1], desc = description)
372 description = "Define to 1 if you have the `%s' type." % t[0]
373 if conf.CheckType(t[0], includes=t[2]):
375 cont += configString('#define %s 1' % t[1], desc = description)
378 cont += configString('/* #undef %s */' % t[1], desc = description)
381 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
382 if type(lib[0]) is type(''):
386 # check if any of the lib exists
388 # if user want the name of the lib detected
390 result[lib[2]] = None
392 if conf.CheckLib(ll):
396 cont += configString('#define %s 1' % lib[1], desc = description)
399 if not result[lib[1]]:
400 cont += configString('/* #undef %s */' % lib[1], desc = description)
402 for test in custom_tests:
406 cont += configString('#define %s 1' % test[1], desc = test[2])
408 cont += configString(test[3], desc = test[2])
412 cont += configString('/* #undef %s */' % test[1], desc = test[2])
414 cont += configString(test[4], desc = test[2])
415 # extra items (no key is returned)
416 for item in extra_items:
417 cont += configString(item[0], desc = item[1])
419 cont += '\n' + config_post + '\n'
421 writeToFile(config_file, cont)
425 def installCygwinLDScript(path):
426 ''' Install i386pe.x-no-rdata '''
427 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
428 script = open(ld_script, 'w')
429 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
431 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
432 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
434 OUTPUT_FORMAT(pei-i386)
435 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
436 ENTRY(_mainCRTStartup)
439 .text __image_base__ + __section_alignment__ :
446 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
447 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
448 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
449 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
451 /* ??? Why is .gcc_exc here? */
456 /* The Cygwin32 library uses a section to avoid copying certain data
457 on fork. This used to be named ".data". The linker used
458 to include this between __data_start__ and __data_end__, but that
459 breaks building the cygwin32 dll. Instead, we name the section
460 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
461 .data BLOCK(__section_alignment__) :
470 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
471 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
472 *(.rdata_runtime_pseudo_reloc)
473 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
474 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
476 *(.data_cygwin_nocopy)
478 .rdata BLOCK(__section_alignment__) :
481 .pdata BLOCK(__section_alignment__) :
485 .bss BLOCK(__section_alignment__) :
492 .edata BLOCK(__section_alignment__) :
503 .idata BLOCK(__section_alignment__) :
505 /* This cannot currently be handled with grouped sections.
506 See pe.em:sort_sections. */
509 /* These zeroes mark the end of the import list. */
510 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
516 .CRT BLOCK(__section_alignment__) :
518 ___crt_xc_start__ = . ;
519 *(SORT(.CRT$XC*)) /* C initialization */
520 ___crt_xc_end__ = . ;
521 ___crt_xi_start__ = . ;
522 *(SORT(.CRT$XI*)) /* C++ initialization */
523 ___crt_xi_end__ = . ;
524 ___crt_xl_start__ = . ;
525 *(SORT(.CRT$XL*)) /* TLS callbacks */
526 /* ___crt_xl_end__ is defined in the TLS Directory support code */
527 ___crt_xp_start__ = . ;
528 *(SORT(.CRT$XP*)) /* Pre-termination */
529 ___crt_xp_end__ = . ;
530 ___crt_xt_start__ = . ;
531 *(SORT(.CRT$XT*)) /* Termination */
532 ___crt_xt_end__ = . ;
534 .tls BLOCK(__section_alignment__) :
542 .endjunk BLOCK(__section_alignment__) :
544 /* end is deprecated, don't use it */
549 .rsrc BLOCK(__section_alignment__) :
554 .reloc BLOCK(__section_alignment__) :
558 .stab BLOCK(__section_alignment__) (NOLOAD) :
562 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
566 /* DWARF debug sections.
567 Symbols in the DWARF debugging sections are relative to the beginning
568 of the section. Unlike other targets that fake this by putting the
569 section VMA at 0, the PE format will not allow it. */
570 /* DWARF 1.1 and DWARF 2. */
571 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
575 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
580 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
582 *(.debug_info) *(.gnu.linkonce.wi.*)
584 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
588 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
592 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
596 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
600 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
604 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
608 /* SGI/MIPS DWARF 2 extensions. */
609 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
613 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
617 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
621 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
626 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
636 def installCygwinPostinstallScript(path):
637 ''' Install lyx.sh '''
638 postinstall_script = os.path.join(path, 'lyx.sh')
639 script = open(postinstall_script, 'w')
640 script.write('''#!/bin/sh
642 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
643 # if it is not already there.
644 if [ -f /etc/fonts/local.conf ]; then
645 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
646 if [ $? -ne 0 ]; then
647 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
648 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
649 fc-cache /usr/share/lyx/fonts
654 return(postinstall_script)
658 # these will be used under win32
664 # does not matter if it fails on other systems
669 def __init__(self, env, logfile, longarg, info):
670 # save the spawn system
672 self.logfile = logfile
673 # clear the logfile (it may not exist)
675 # this will overwrite existing content.
676 writeToFile(logfile, info, append=False)
678 self.longarg = longarg
679 # get hold of the old spawn? (necessary?)
680 self._spawn = env['SPAWN']
683 def spawn(self, sh, escape, cmd, args, spawnenv):
685 newargs = ' '.join(map(escape, args[1:]))
686 cmdline = cmd + " " + newargs
688 # if log is not empty, write to it
689 if self.logfile != '':
690 # this tend to be slow (?) but ensure correct output
691 # Note that cmdline may be long so I do not escape it
693 # since this is not an essential operation, proceed if things go wrong here.
694 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
696 print "Warning: can not write to log file ", self.logfile
698 # if the command is not too long, use the old
699 if not self.longarg or len(cmdline) < 8000:
700 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
702 sAttrs = win32security.SECURITY_ATTRIBUTES()
703 StartupInfo = win32process.STARTUPINFO()
705 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
706 # check for any special operating system commands
709 win32file.DeleteFile(arg)
712 # otherwise execute the command.
713 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
714 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
715 exit_code = win32process.GetExitCodeProcess(hProcess)
716 win32file.CloseHandle(hProcess);
717 win32file.CloseHandle(hThread);
721 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
722 ''' This function modify env and allow logging of
723 commands to a logfile. If the argument is too long
724 a win32 spawn will be used instead of the system one
727 # create a new spwn object
728 ls = loggedSpawn(env, logfile, longarg, info)
729 # replace the old SPAWN by the new function
730 env['SPAWN'] = ls.spawn