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 *
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)
73 target_file.write(contents + "\n")
75 #st = os.stat(str(source[0]))
76 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
79 def env_nsis(source, target, env, for_signature):
80 ''' Get nsis command line '''
81 def quoteIfSpaced(str):
83 return '"' + str + '"'
86 ret = env['NSIS'] + " /V1 "
87 if env.has_key('NSISFLAGS'):
88 for flag in env['NSISFLAGS']:
91 if env.has_key('NSISDEFINES'):
92 for d in env['NSISDEFINES']:
94 if env['NSISDEFINES'][d]:
95 ret += '=' + quoteIfSpaced(env['NSISDEFINES'][d])
98 if '-bundle.exe' in str(target[0]):
99 ret += '/DSETUPTYPE_BUNDLE=1 '
101 ret += quoteIfSpaced(str(s))
105 def env_toc(target, source, env):
106 '''Generate target from source files'''
107 # this is very tricky because we need to use installed lyx2lyx with
108 # correct lyx2lyx_version.py
109 sys.path.append(env['LYX2LYX_DEST'])
110 sys.path.append(env.Dir('$TOP_SRCDIR/lib/doc').abspath)
113 doc_toc.build_toc(str(target[0]), [file.abspath for file in source])
116 def createResFromIcon(env, icon_file, rc_file):
117 ''' create a rc file with icon, and return res file (windows only) '''
119 rc_name = env.File(rc_file).abspath
120 dir = os.path.split(rc_name)[0]
121 if not os.path.isdir(dir):
123 rc = open(rc_name, 'w')
124 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
125 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
126 'packaging', 'icons', icon_file).replace('\\', '\\\\')
128 return env.RES(rc_name)
137 def checkPkgConfig(conf, version):
138 ''' Return false if pkg_config does not exist, or is too old '''
139 conf.Message('Checking for pkg-config...')
140 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
145 def checkPackage(conf, pkg):
146 ''' check if pkg is under the control of conf '''
147 conf.Message('Checking for package %s...' % pkg)
148 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
153 def checkMkdirOneArg(conf):
154 check_mkdir_one_arg_source = """
155 #include <sys/stat.h>
161 conf.Message('Checking for the number of args for mkdir... ')
162 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
163 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
164 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
172 def checkCXXGlobalCstd(conf):
173 ''' Checking the use of std::tolower or tolower '''
174 check_global_cstd_source = '''
182 conf.Message('Checking for the use of global cstd... ')
183 ret = conf.TryLink(check_global_cstd_source, '.c')
188 def checkSelectArgType(conf):
189 ''' Adapted from autoconf '''
190 conf.Message('Checking for arg types for select... ')
191 for arg234 in ['fd_set *', 'int *', 'void *']:
192 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
193 for arg5 in ['struct timeval *', 'const struct timeval *']:
194 check_select_source = '''
195 #if HAVE_SYS_SELECT_H
196 # include <sys/select.h>
198 #if HAVE_SYS_SOCKET_H
199 # include <sys/socket.h>
201 extern int select (%s, %s, %s, %s, %s);
206 ''' % (arg1, arg234, arg234, arg234, arg5)
207 ret = conf.TryLink(check_select_source, '.c')
210 return (arg1, arg234, arg5)
211 conf.Result('no (use default)')
212 return ('int', 'int *', 'struct timeval *')
215 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
216 ''' look for boost libraries
218 lib_paths: try these paths for boost libraries
219 inc_paths: try these paths for boost headers
220 versions: supported boost versions
221 isDebug: if true, use debug libraries
223 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
224 libprefix = conf.env['LIBPREFIX']
225 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
231 for path in lib_paths:
232 conf.Log("Looking into %s\n" % path)
234 # get all the libs, then filter for the right library
235 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
236 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
238 conf.Log("Find boost libraries: %s\n" % files)
239 # runtime code includes s,g,y,d,p,n, where we should look for
240 # d,g,y for debug, s,p,n for release
244 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
247 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
248 if len(lib_files) == 0:
249 # use alternative libraries
251 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
252 if len(lib_files) > 0:
253 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
254 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
255 lib_names.append(name.split('.')[0])
256 conf.Log("Qualified libraries: %s\n" % lib_names)
258 conf.Log("No qualified library is found.\n")
260 if len(lib_names) == len(libs):
265 if len(lib_names) == 0:
266 conf.Log("No boost library is found\n")
268 conf.Log("Found boost libraries: %s\n" % lib_names)
270 return (None, None, None)
271 # check version number in boost/version.hpp
272 def isValidBoostDir(dir):
273 version_file = os.path.join(dir, 'boost', 'version.hpp')
274 if not os.path.isfile(version_file):
276 version_file_content = open(version_file).read()
277 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
278 return True in [x in version_file_content for x in version_strings]
279 # check for boost header file
280 for path in inc_paths:
281 conf.Log("Checking for inc path: %s\n" % path)
282 if isValidBoostDir(path):
285 else: # check path/boost_1_xx_x/boost
286 dirs = glob.glob(os.path.join(path, 'boost-*'))
287 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
288 conf.Log("Checing for sub directory: %s\n" % dirs[0])
294 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
295 return (lib_names, lib_path, inc_path)
298 return (None, None, None)
301 def checkCommand(conf, cmd):
302 ''' check the existence of a command
303 return full path to the command, or none
305 conf.Message('Checking for command %s...' % cmd)
307 conf.Result(res is not None)
312 ''' check the existence of nsis compiler, return the fullpath '''
313 conf.Message('Checking for nsis compiler...')
316 # If we can read the registry, get the NSIS command from it
318 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
320 val, tok = RegQueryValueEx(k,None)
321 ret = val + os.path.sep + 'makensis.exe'
322 if os.path.isfile(ret):
323 res = '"' + ret + '"'
327 pass # Couldn't find the key, just act like we can't read the registry
328 # Hope it's on the path
330 res = WhereIs('makensis.exe')
331 conf.Result(res is not None)
335 def checkLC_MESSAGES(conf):
336 ''' check the definition of LC_MESSAGES '''
337 check_LC_MESSAGES = '''
344 conf.Message('Checking for LC_MESSAGES in locale.h... ')
345 ret = conf.TryLink(check_LC_MESSAGES, '.c')
350 def checkIconvConst(conf):
351 ''' check the declaration of iconv '''
352 check_iconv_const = '''
354 // this declaration will fail when there already exists a non const char**
355 // version which returns size_t
356 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
361 conf.Message('Checking if the declaration of iconv needs const... ')
362 ret = conf.TryLink(check_iconv_const, '.c')
367 def checkSizeOfWChar(conf):
368 ''' check the size of wchar '''
369 check_sizeof_wchar = '''
370 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
376 conf.Message('Checking the size of wchar_t... ')
377 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
379 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
383 conf.Result(str(ret))
387 def createConfigFile(conf, config_file,
388 config_pre = '', config_post = '',
389 headers = [], functions = [], types = [], libs = [],
390 custom_tests = [], extra_items = []):
391 ''' create a configuration file, with options
392 config_file: which file to create
393 config_pre: first part of the config file
394 config_post: last part of the config file
395 headers: header files to check, in the form of a list of
396 ('file', 'HAVE_FILE', 'c'/'c++')
397 functions: functions to check, in the form of a list of
398 ('func', 'HAVE_func', 'include lines'/None)
399 types: types to check, in the form of a list of
400 ('type', 'HAVE_TYPE', 'includelines'/None)
401 libs: libraries to check, in the form of a list of
402 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
403 or any of the libs exists if 'lib' is a list of libs.
404 Optionally, user can provide another key LIB_NAME, that will
405 be set to the detected lib (or None otherwise).
406 custom_tests: extra tests to perform, in the form of a list of
407 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
408 If the last two are ignored, '#define key 1' '/*#undef key */'
410 extra_items: extra configuration lines, in the form of a list of
411 ('config', 'description')
413 The result of each test, as a dictioanry of
414 res['XXX'] = True/False
415 XXX are keys defined in each argument.
417 cont = config_pre + '\n'
419 # add to this string, in appropriate format
420 def configString(lines, desc=''):
422 if lines.strip() != '':
424 text += '/* ' + desc + ' */\n'
425 text += lines + '\n\n'
429 for header in headers:
430 description = "Define to 1 if you have the <%s> header file." % header[0]
431 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
432 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
433 result[header[1]] = 1
434 cont += configString('#define %s 1' % header[1], desc = description)
436 result[header[1]] = 0
437 cont += configString('/* #undef %s */' % header[1], desc = description)
439 for func in functions:
440 description = "Define to 1 if you have the `%s' function." % func[0]
441 if conf.CheckFunc(func[0], header=func[2]):
443 cont += configString('#define %s 1' % func[1], desc = description)
446 cont += configString('/* #undef %s */' % func[1], desc = description)
449 description = "Define to 1 if you have the `%s' type." % t[0]
450 if conf.CheckType(t[0], includes=t[2]):
452 cont += configString('#define %s 1' % t[1], desc = description)
455 cont += configString('/* #undef %s */' % t[1], desc = description)
458 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
459 if type(lib[0]) is type(''):
463 # check if any of the lib exists
465 # if user want the name of the lib detected
467 result[lib[2]] = None
469 if conf.CheckLib(ll):
473 cont += configString('#define %s 1' % lib[1], desc = description)
476 if not result[lib[1]]:
477 cont += configString('/* #undef %s */' % lib[1], desc = description)
479 for test in custom_tests:
483 cont += configString('#define %s 1' % test[1], desc = test[2])
485 cont += configString(test[3], desc = test[2])
489 cont += configString('/* #undef %s */' % test[1], desc = test[2])
491 cont += configString(test[4], desc = test[2])
492 # extra items (no key is returned)
493 for item in extra_items:
494 cont += configString(item[0], desc = item[1])
496 cont += '\n' + config_post + '\n'
498 writeToFile(config_file, cont)
502 def installCygwinLDScript(path):
503 ''' Install i386pe.x-no-rdata '''
504 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
505 script = open(ld_script, 'w')
506 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
508 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
509 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
511 OUTPUT_FORMAT(pei-i386)
512 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
513 ENTRY(_mainCRTStartup)
516 .text __image_base__ + __section_alignment__ :
523 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
524 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
525 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
526 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
528 /* ??? Why is .gcc_exc here? */
533 /* The Cygwin32 library uses a section to avoid copying certain data
534 on fork. This used to be named ".data". The linker used
535 to include this between __data_start__ and __data_end__, but that
536 breaks building the cygwin32 dll. Instead, we name the section
537 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
538 .data BLOCK(__section_alignment__) :
547 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
548 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
549 *(.rdata_runtime_pseudo_reloc)
550 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
551 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
553 *(.data_cygwin_nocopy)
555 .rdata BLOCK(__section_alignment__) :
558 .pdata BLOCK(__section_alignment__) :
562 .bss BLOCK(__section_alignment__) :
569 .edata BLOCK(__section_alignment__) :
580 .idata BLOCK(__section_alignment__) :
582 /* This cannot currently be handled with grouped sections.
583 See pe.em:sort_sections. */
586 /* These zeroes mark the end of the import list. */
587 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
593 .CRT BLOCK(__section_alignment__) :
595 ___crt_xc_start__ = . ;
596 *(SORT(.CRT$XC*)) /* C initialization */
597 ___crt_xc_end__ = . ;
598 ___crt_xi_start__ = . ;
599 *(SORT(.CRT$XI*)) /* C++ initialization */
600 ___crt_xi_end__ = . ;
601 ___crt_xl_start__ = . ;
602 *(SORT(.CRT$XL*)) /* TLS callbacks */
603 /* ___crt_xl_end__ is defined in the TLS Directory support code */
604 ___crt_xp_start__ = . ;
605 *(SORT(.CRT$XP*)) /* Pre-termination */
606 ___crt_xp_end__ = . ;
607 ___crt_xt_start__ = . ;
608 *(SORT(.CRT$XT*)) /* Termination */
609 ___crt_xt_end__ = . ;
611 .tls BLOCK(__section_alignment__) :
619 .endjunk BLOCK(__section_alignment__) :
621 /* end is deprecated, don't use it */
626 .rsrc BLOCK(__section_alignment__) :
631 .reloc BLOCK(__section_alignment__) :
635 .stab BLOCK(__section_alignment__) (NOLOAD) :
639 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
643 /* DWARF debug sections.
644 Symbols in the DWARF debugging sections are relative to the beginning
645 of the section. Unlike other targets that fake this by putting the
646 section VMA at 0, the PE format will not allow it. */
647 /* DWARF 1.1 and DWARF 2. */
648 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
652 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
657 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
659 *(.debug_info) *(.gnu.linkonce.wi.*)
661 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
665 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
669 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
673 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
677 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
681 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
685 /* SGI/MIPS DWARF 2 extensions. */
686 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
690 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
694 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
698 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
703 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
713 def installCygwinPostinstallScript(path):
714 ''' Install lyx.sh '''
715 postinstall_script = os.path.join(path, 'lyx.sh')
716 script = open(postinstall_script, 'w')
717 script.write(r'''#!/bin/sh
719 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
720 # if it is not already there.
721 if [ -f /etc/fonts/local.conf ]; then
722 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
723 if [ $? -ne 0 ]; then
724 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
725 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
726 fc-cache /usr/share/lyx/fonts
731 return(postinstall_script)
735 # these will be used under win32
741 # does not matter if it fails on other systems
746 def __init__(self, env, logfile, longarg, info):
747 # save the spawn system
749 self.logfile = logfile
750 # clear the logfile (it may not exist)
752 # this will overwrite existing content.
753 writeToFile(logfile, info, append=False)
755 self.longarg = longarg
756 # get hold of the old spawn? (necessary?)
757 self._spawn = env['SPAWN']
760 def spawn(self, sh, escape, cmd, args, spawnenv):
762 newargs = ' '.join(map(escape, args[1:]))
763 cmdline = cmd + " " + newargs
765 # if log is not empty, write to it
766 if self.logfile != '':
767 # this tend to be slow (?) but ensure correct output
768 # Note that cmdline may be long so I do not escape it
770 # since this is not an essential operation, proceed if things go wrong here.
771 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
773 print "Warning: can not write to log file ", self.logfile
775 # if the command is not too long, use the old
776 if not self.longarg or len(cmdline) < 8000:
777 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
779 sAttrs = win32security.SECURITY_ATTRIBUTES()
780 StartupInfo = win32process.STARTUPINFO()
782 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
783 # check for any special operating system commands
786 win32file.DeleteFile(arg)
789 # otherwise execute the command.
790 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
791 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
792 exit_code = win32process.GetExitCodeProcess(hProcess)
793 win32file.CloseHandle(hProcess);
794 win32file.CloseHandle(hThread);
798 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
799 ''' This function modify env and allow logging of
800 commands to a logfile. If the argument is too long
801 a win32 spawn will be used instead of the system one
804 # create a new spwn object
805 ls = loggedSpawn(env, logfile, longarg, info)
806 # replace the old SPAWN by the new function
807 env['SPAWN'] = ls.spawn