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))
170 libprefix = conf.env['LIBPREFIX']
171 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
177 for path in lib_paths:
178 conf.Log("Looking into %s\n" % path)
180 # get all the libs, then filter for the right library
181 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
182 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
184 conf.Log("Find boost libraries: %s\n" % files)
185 # runtime code includes s,g,y,d,p,n, where we should look for
186 # d,g,y for debug, s,p,n for release
190 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
193 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
194 if len(lib_files) == 0:
195 # use alternative libraries
197 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
198 if len(lib_files) > 0:
199 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
200 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
201 lib_names.append(name.split('.')[0])
202 conf.Log("Qualified libraries: %s\n" % lib_names)
204 conf.Log("No qualified library is found.\n")
206 if len(lib_names) == len(libs):
211 if len(lib_names) == 0:
212 conf.Log("No boost library is found\n")
214 conf.Log("Found boost libraries: %s\n" % lib_names)
216 return (None, None, None)
217 # check version number in boost/version.hpp
218 def isValidBoostDir(dir):
219 version_file = os.path.join(dir, 'boost', 'version.hpp')
220 if not os.path.isfile(version_file):
222 version_file_content = open(version_file).read()
223 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
224 return True in [x in version_file_content for x in version_strings]
225 # check for boost header file
226 for path in inc_paths:
227 conf.Log("Checking for inc path: %s\n" % path)
228 if isValidBoostDir(path):
231 else: # check path/boost_1_xx_x/boost
232 dirs = glob.glob(os.path.join(path, 'boost-*'))
233 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
234 conf.Log("Checing for sub directory: %s\n" % dirs[0])
240 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
241 return (lib_names, lib_path, inc_path)
244 return (None, None, None)
247 def checkCommand(conf, cmd):
248 ''' check the existence of a command
249 return full path to the command, or none
251 conf.Message('Checking for command %s...' % cmd)
253 conf.Result(res is not None)
257 def checkLC_MESSAGES(conf):
258 ''' check the definition of LC_MESSAGES '''
259 check_LC_MESSAGES = '''
266 conf.Message('Check for LC_MESSAGES in locale.h... ')
267 ret = conf.TryLink(check_LC_MESSAGES, '.c')
272 def checkIconvConst(conf):
273 ''' check the declaration of iconv '''
274 check_iconv_const = '''
276 // this declaration will fail when there already exists a non const char**
277 // version which returns size_t
278 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
283 conf.Message('Check if the declaration of iconv needs const... ')
284 ret = conf.TryLink(check_iconv_const, '.c')
289 def checkSizeOfWChar(conf):
290 ''' check the size of wchar '''
291 check_sizeof_wchar = '''
292 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
298 conf.Message('Check the size of wchar_t... ')
299 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
301 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
305 conf.Result(str(ret))
309 def createConfigFile(conf, config_file,
310 config_pre = '', config_post = '',
311 headers = [], functions = [], types = [], libs = [],
312 custom_tests = [], extra_items = []):
313 ''' create a configuration file, with options
314 config_file: which file to create
315 config_pre: first part of the config file
316 config_post: last part of the config file
317 headers: header files to check, in the form of a list of
318 ('file', 'HAVE_FILE', 'c'/'c++')
319 functions: functions to check, in the form of a list of
320 ('func', 'HAVE_func', 'include lines'/None)
321 types: types to check, in the form of a list of
322 ('type', 'HAVE_TYPE', 'includelines'/None)
323 libs: libraries to check, in the form of a list of
324 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
325 or any of the libs exists if 'lib' is a list of libs.
326 Optionally, user can provide another key LIB_NAME, that will
327 be set to the detected lib (or None otherwise).
328 custom_tests: extra tests to perform, in the form of a list of
329 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
330 If the last two are ignored, '#define key 1' '/*#undef key */'
332 extra_items: extra configuration lines, in the form of a list of
333 ('config', 'description')
335 The result of each test, as a dictioanry of
336 res['XXX'] = True/False
337 XXX are keys defined in each argument.
339 cont = config_pre + '\n'
341 # add to this string, in appropriate format
342 def configString(lines, desc=''):
344 if lines.strip() != '':
346 text += '/* ' + desc + ' */\n'
347 text += lines + '\n\n'
351 for header in headers:
352 description = "Define to 1 if you have the <%s> header file." % header[0]
353 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
354 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
355 result[header[1]] = 1
356 cont += configString('#define %s 1' % header[1], desc = description)
358 result[header[1]] = 0
359 cont += configString('/* #undef %s */' % header[1], desc = description)
361 for func in functions:
362 description = "Define to 1 if you have the `%s' function." % func[0]
363 if conf.CheckFunc(func[0], header=func[2]):
365 cont += configString('#define %s 1' % func[1], desc = description)
368 cont += configString('/* #undef %s */' % func[1], desc = description)
371 description = "Define to 1 if you have the `%s' type." % t[0]
372 if conf.CheckType(t[0], includes=t[2]):
374 cont += configString('#define %s 1' % t[1], desc = description)
377 cont += configString('/* #undef %s */' % t[1], desc = description)
380 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
381 if type(lib[0]) is type(''):
385 # check if any of the lib exists
387 # if user want the name of the lib detected
389 result[lib[2]] = None
391 if conf.CheckLib(ll):
395 cont += configString('#define %s 1' % lib[1], desc = description)
398 if not result[lib[1]]:
399 cont += configString('/* #undef %s */' % lib[1], desc = description)
401 for test in custom_tests:
405 cont += configString('#define %s 1' % test[1], desc = test[2])
407 cont += configString(test[3], desc = test[2])
411 cont += configString('/* #undef %s */' % test[1], desc = test[2])
413 cont += configString(test[4], desc = test[2])
414 # extra items (no key is returned)
415 for item in extra_items:
416 cont += configString(item[0], desc = item[1])
418 cont += '\n' + config_post + '\n'
420 writeToFile(config_file, cont)
424 def installCygwinLDScript(path):
425 ''' Install i386pe.x-no-rdata '''
426 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
427 script = open(ld_script, 'w')
428 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
430 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
431 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
433 OUTPUT_FORMAT(pei-i386)
434 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
435 ENTRY(_mainCRTStartup)
438 .text __image_base__ + __section_alignment__ :
445 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
446 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
447 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
448 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
450 /* ??? Why is .gcc_exc here? */
455 /* The Cygwin32 library uses a section to avoid copying certain data
456 on fork. This used to be named ".data". The linker used
457 to include this between __data_start__ and __data_end__, but that
458 breaks building the cygwin32 dll. Instead, we name the section
459 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
460 .data BLOCK(__section_alignment__) :
469 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
470 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
471 *(.rdata_runtime_pseudo_reloc)
472 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
473 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
475 *(.data_cygwin_nocopy)
477 .rdata BLOCK(__section_alignment__) :
480 .pdata BLOCK(__section_alignment__) :
484 .bss BLOCK(__section_alignment__) :
491 .edata BLOCK(__section_alignment__) :
502 .idata BLOCK(__section_alignment__) :
504 /* This cannot currently be handled with grouped sections.
505 See pe.em:sort_sections. */
508 /* These zeroes mark the end of the import list. */
509 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
515 .CRT BLOCK(__section_alignment__) :
517 ___crt_xc_start__ = . ;
518 *(SORT(.CRT$XC*)) /* C initialization */
519 ___crt_xc_end__ = . ;
520 ___crt_xi_start__ = . ;
521 *(SORT(.CRT$XI*)) /* C++ initialization */
522 ___crt_xi_end__ = . ;
523 ___crt_xl_start__ = . ;
524 *(SORT(.CRT$XL*)) /* TLS callbacks */
525 /* ___crt_xl_end__ is defined in the TLS Directory support code */
526 ___crt_xp_start__ = . ;
527 *(SORT(.CRT$XP*)) /* Pre-termination */
528 ___crt_xp_end__ = . ;
529 ___crt_xt_start__ = . ;
530 *(SORT(.CRT$XT*)) /* Termination */
531 ___crt_xt_end__ = . ;
533 .tls BLOCK(__section_alignment__) :
541 .endjunk BLOCK(__section_alignment__) :
543 /* end is deprecated, don't use it */
548 .rsrc BLOCK(__section_alignment__) :
553 .reloc BLOCK(__section_alignment__) :
557 .stab BLOCK(__section_alignment__) (NOLOAD) :
561 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
565 /* DWARF debug sections.
566 Symbols in the DWARF debugging sections are relative to the beginning
567 of the section. Unlike other targets that fake this by putting the
568 section VMA at 0, the PE format will not allow it. */
569 /* DWARF 1.1 and DWARF 2. */
570 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
574 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
579 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
581 *(.debug_info) *(.gnu.linkonce.wi.*)
583 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
587 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
591 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
595 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
599 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
603 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
607 /* SGI/MIPS DWARF 2 extensions. */
608 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
612 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
616 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
620 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
625 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
635 def installCygwinPostinstallScript(path):
636 ''' Install lyx.sh '''
637 postinstall_script = os.path.join(path, 'lyx.sh')
638 script = open(postinstall_script, 'w')
639 script.write('''#!/bin/sh
641 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
642 # if it is not already there.
643 if [ -f /etc/fonts/local.conf ]; then
644 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
645 if [ $? -ne 0 ]; then
646 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
647 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
648 fc-cache /usr/share/lyx/fonts
653 return(postinstall_script)
657 # these will be used under win32
663 # does not matter if it fails on other systems
668 def __init__(self, env, logfile, longarg, info):
669 # save the spawn system
671 self.logfile = logfile
672 # clear the logfile (it may not exist)
674 # this will overwrite existing content.
675 writeToFile(logfile, info, append=False)
677 self.longarg = longarg
678 # get hold of the old spawn? (necessary?)
679 self._spawn = env['SPAWN']
682 def spawn(self, sh, escape, cmd, args, spawnenv):
684 newargs = ' '.join(map(escape, args[1:]))
685 cmdline = cmd + " " + newargs
687 # if log is not empty, write to it
688 if self.logfile != '':
689 # this tend to be slow (?) but ensure correct output
690 # Note that cmdline may be long so I do not escape it
692 # since this is not an essential operation, proceed if things go wrong here.
693 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
695 print "Warning: can not write to log file ", self.logfile
697 # if the command is not too long, use the old
698 if not self.longarg or len(cmdline) < 8000:
699 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
701 sAttrs = win32security.SECURITY_ATTRIBUTES()
702 StartupInfo = win32process.STARTUPINFO()
704 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
705 # check for any special operating system commands
708 win32file.DeleteFile(arg)
711 # otherwise execute the command.
712 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
713 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
714 exit_code = win32process.GetExitCodeProcess(hProcess)
715 win32file.CloseHandle(hProcess);
716 win32file.CloseHandle(hThread);
720 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
721 ''' This function modify env and allow logging of
722 commands to a logfile. If the argument is too long
723 a win32 spawn will be used instead of the system one
726 # create a new spwn object
727 ls = loggedSpawn(env, logfile, longarg, info)
728 # replace the old SPAWN by the new function
729 env['SPAWN'] = ls.spawn