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:
199 print 'Warning: Can not find an appropriate boost library in %s.' % path
200 print 'Allowed versions are ', ', '.join(versions)
202 lib_files += filter(lambda x: re.search('libboost_%s-[\w-]+%s.a' % (lib, ver), x), files)
203 if len(lib_files) > 0:
204 print 'Use library %s' % lib_files[0]
205 if len(lib_files) > 0:
206 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
207 lib_names.append(lib_files[0].split(os.sep)[-1][3:-2])
208 if len(lib_names) == len(libs):
214 return (None, None, None)
215 # check version number in boost/version.hpp
216 def isValidBoostDir(dir):
217 version_file = os.path.join(dir, 'boost', 'version.hpp')
218 if not os.path.isfile(version_file):
220 version_file_content = open(version_file).read()
221 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
222 return True in [x in version_file_content for x in version_strings]
223 # check for boost header file
224 for path in inc_paths:
225 if isValidBoostDir(path):
228 else: # check path/boost_1_xx_x/boost
229 dirs = glob.glob(os.path.join(path, 'boost-*'))
230 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
236 print 'Using boost libraries', ', '.join(lib_names)
237 return (lib_names, lib_path, inc_path)
240 return (None, None, None)
243 def checkCommand(conf, cmd):
244 ''' check the existence of a command
245 return full path to the command, or none
247 conf.Message('Checking for command %s...' % cmd)
249 conf.Result(res is not None)
253 def checkLC_MESSAGES(conf):
254 ''' check the definition of LC_MESSAGES '''
255 check_LC_MESSAGES = '''
262 conf.Message('Check for LC_MESSAGES in locale.h... ')
263 ret = conf.TryLink(check_LC_MESSAGES, '.c')
268 def checkIconvConst(conf):
269 ''' check the declaration of iconv '''
270 check_iconv_const = '''
272 // this declaration will fail when there already exists a non const char**
273 // version which returns size_t
274 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
279 conf.Message('Check if the declaration of iconv needs const... ')
280 ret = conf.TryLink(check_iconv_const, '.c')
285 def checkSizeOfWChar(conf):
286 ''' check the size of wchar '''
287 check_sizeof_wchar = '''
288 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
294 conf.Message('Check the size of wchar_t... ')
295 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
297 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
301 conf.Result(str(ret))
305 def createConfigFile(conf, config_file,
306 config_pre = '', config_post = '',
307 headers = [], functions = [], types = [], libs = [],
308 custom_tests = [], extra_items = []):
309 ''' create a configuration file, with options
310 config_file: which file to create
311 config_pre: first part of the config file
312 config_post: last part of the config file
313 headers: header files to check, in the form of a list of
314 ('file', 'HAVE_FILE', 'c'/'c++')
315 functions: functions to check, in the form of a list of
316 ('func', 'HAVE_func', 'include lines'/None)
317 types: types to check, in the form of a list of
318 ('type', 'HAVE_TYPE', 'includelines'/None)
319 libs: libraries to check, in the form of a list of
320 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
321 or any of the libs exists if 'lib' is a list of libs.
322 Optionally, user can provide another key LIB_NAME, that will
323 be set to the detected lib (or None otherwise).
324 custom_tests: extra tests to perform, in the form of a list of
325 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
326 If the last two are ignored, '#define key 1' '/*#undef key */'
328 extra_items: extra configuration lines, in the form of a list of
329 ('config', 'description')
331 The result of each test, as a dictioanry of
332 res['XXX'] = True/False
333 XXX are keys defined in each argument.
335 cont = config_pre + '\n'
337 # add to this string, in appropriate format
338 def configString(lines, desc=''):
340 if lines.strip() != '':
342 text += '/* ' + desc + ' */\n'
343 text += lines + '\n\n'
347 for header in headers:
348 description = "Define to 1 if you have the <%s> header file." % header[0]
349 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
350 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
351 result[header[1]] = 1
352 cont += configString('#define %s 1' % header[1], desc = description)
354 result[header[1]] = 0
355 cont += configString('/* #undef %s */' % header[1], desc = description)
357 for func in functions:
358 description = "Define to 1 if you have the `%s' function." % func[0]
359 if conf.CheckFunc(func[0], header=func[2]):
361 cont += configString('#define %s 1' % func[1], desc = description)
364 cont += configString('/* #undef %s */' % func[1], desc = description)
367 description = "Define to 1 if you have the `%s' type." % t[0]
368 if conf.CheckType(t[0], includes=t[2]):
370 cont += configString('#define %s 1' % t[1], desc = description)
373 cont += configString('/* #undef %s */' % t[1], desc = description)
376 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
377 if type(lib[0]) is type(''):
381 # check if any of the lib exists
383 # if user want the name of the lib detected
385 result[lib[2]] = None
387 if conf.CheckLib(ll):
391 cont += configString('#define %s 1' % lib[1], desc = description)
394 if not result[lib[1]]:
395 cont += configString('/* #undef %s */' % lib[1], desc = description)
397 for test in custom_tests:
401 cont += configString('#define %s 1' % test[1], desc = test[2])
403 cont += configString(test[3], desc = test[2])
407 cont += configString('/* #undef %s */' % test[1], desc = test[2])
409 cont += configString(test[4], desc = test[2])
410 # extra items (no key is returned)
411 for item in extra_items:
412 cont += configString(item[0], desc = item[1])
414 cont += '\n' + config_post + '\n'
416 writeToFile(config_file, cont)
420 def installCygwinLDScript(path):
421 ''' Install i386pe.x-no-rdata '''
422 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
423 script = open(ld_script, 'w')
424 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
426 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
427 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
429 OUTPUT_FORMAT(pei-i386)
430 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
431 ENTRY(_mainCRTStartup)
434 .text __image_base__ + __section_alignment__ :
441 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
442 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
443 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
444 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
446 /* ??? Why is .gcc_exc here? */
451 /* The Cygwin32 library uses a section to avoid copying certain data
452 on fork. This used to be named ".data". The linker used
453 to include this between __data_start__ and __data_end__, but that
454 breaks building the cygwin32 dll. Instead, we name the section
455 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
456 .data BLOCK(__section_alignment__) :
465 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
466 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
467 *(.rdata_runtime_pseudo_reloc)
468 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
469 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
471 *(.data_cygwin_nocopy)
473 .rdata BLOCK(__section_alignment__) :
476 .pdata BLOCK(__section_alignment__) :
480 .bss BLOCK(__section_alignment__) :
487 .edata BLOCK(__section_alignment__) :
498 .idata BLOCK(__section_alignment__) :
500 /* This cannot currently be handled with grouped sections.
501 See pe.em:sort_sections. */
504 /* These zeroes mark the end of the import list. */
505 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
511 .CRT BLOCK(__section_alignment__) :
513 ___crt_xc_start__ = . ;
514 *(SORT(.CRT$XC*)) /* C initialization */
515 ___crt_xc_end__ = . ;
516 ___crt_xi_start__ = . ;
517 *(SORT(.CRT$XI*)) /* C++ initialization */
518 ___crt_xi_end__ = . ;
519 ___crt_xl_start__ = . ;
520 *(SORT(.CRT$XL*)) /* TLS callbacks */
521 /* ___crt_xl_end__ is defined in the TLS Directory support code */
522 ___crt_xp_start__ = . ;
523 *(SORT(.CRT$XP*)) /* Pre-termination */
524 ___crt_xp_end__ = . ;
525 ___crt_xt_start__ = . ;
526 *(SORT(.CRT$XT*)) /* Termination */
527 ___crt_xt_end__ = . ;
529 .tls BLOCK(__section_alignment__) :
537 .endjunk BLOCK(__section_alignment__) :
539 /* end is deprecated, don't use it */
544 .rsrc BLOCK(__section_alignment__) :
549 .reloc BLOCK(__section_alignment__) :
553 .stab BLOCK(__section_alignment__) (NOLOAD) :
557 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
561 /* DWARF debug sections.
562 Symbols in the DWARF debugging sections are relative to the beginning
563 of the section. Unlike other targets that fake this by putting the
564 section VMA at 0, the PE format will not allow it. */
565 /* DWARF 1.1 and DWARF 2. */
566 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
570 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
575 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
577 *(.debug_info) *(.gnu.linkonce.wi.*)
579 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
583 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
587 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
591 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
595 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
599 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
603 /* SGI/MIPS DWARF 2 extensions. */
604 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
608 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
612 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
616 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
621 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
631 def installCygwinPostinstallScript(path):
632 ''' Install lyx.sh '''
633 postinstall_script = os.path.join(path, 'lyx.sh')
634 script = open(postinstall_script, 'w')
635 script.write('''#!/bin/sh
637 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
638 # if it is not already there.
639 if [ -f /etc/fonts/local.conf ]; then
640 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
641 if [ $? -ne 0 ]; then
642 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
643 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
644 fc-cache /usr/share/lyx/fonts
649 return(postinstall_script)
653 # these will be used under win32
659 # does not matter if it fails on other systems
664 def __init__(self, env, logfile, longarg, info):
665 # save the spawn system
667 self.logfile = logfile
668 # clear the logfile (it may not exist)
670 # this will overwrite existing content.
671 writeToFile(logfile, info, append=False)
673 self.longarg = longarg
674 # get hold of the old spawn? (necessary?)
675 self._spawn = env['SPAWN']
678 def spawn(self, sh, escape, cmd, args, spawnenv):
680 newargs = ' '.join(map(escape, args[1:]))
681 cmdline = cmd + " " + newargs
683 # if log is not empty, write to it
684 if self.logfile != '':
685 # this tend to be slow (?) but ensure correct output
686 # Note that cmdline may be long so I do not escape it
688 # since this is not an essential operation, proceed if things go wrong here.
689 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
691 print "Warning: can not write to log file ", self.logfile
693 # if the command is not too long, use the old
694 if not self.longarg or len(cmdline) < 8000:
695 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
697 sAttrs = win32security.SECURITY_ATTRIBUTES()
698 StartupInfo = win32process.STARTUPINFO()
700 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
701 # check for any special operating system commands
704 win32file.DeleteFile(arg)
707 # otherwise execute the command.
708 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
709 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
710 exit_code = win32process.GetExitCodeProcess(hProcess)
711 win32file.CloseHandle(hProcess);
712 win32file.CloseHandle(hThread);
716 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
717 ''' This function modify env and allow logging of
718 commands to a logfile. If the argument is too long
719 a win32 spawn will be used instead of the system one
722 # create a new spwn object
723 ls = loggedSpawn(env, logfile, longarg, info)
724 # replace the old SPAWN by the new function
725 env['SPAWN'] = ls.spawn