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 env_cat(target, source, env):
117 '''Cat source > target. Avoid pipe to increase portability'''
118 output = open(env.File(target[0]).abspath, 'w')
120 input = open(env.File(src).abspath)
121 output.write(input.read())
126 def createResFromIcon(env, icon_file, rc_file):
127 ''' create a rc file with icon, and return res file (windows only) '''
129 rc_name = env.File(rc_file).abspath
130 dir = os.path.split(rc_name)[0]
131 if not os.path.isdir(dir):
133 rc = open(rc_name, 'w')
134 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
135 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
136 'packaging', 'icons', icon_file).replace('\\', '\\\\')
138 return env.RES(rc_name)
147 def checkPkgConfig(conf, version):
148 ''' Return false if pkg_config does not exist, or is too old '''
149 conf.Message('Checking for pkg-config...')
150 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
155 def checkPackage(conf, pkg):
156 ''' check if pkg is under the control of conf '''
157 conf.Message('Checking for package %s...' % pkg)
158 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
163 def checkMkdirOneArg(conf):
164 check_mkdir_one_arg_source = """
165 #include <sys/stat.h>
171 conf.Message('Checking for the number of args for mkdir... ')
172 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
173 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
174 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
182 def checkCXXGlobalCstd(conf):
183 ''' Checking the use of std::tolower or tolower '''
184 check_global_cstd_source = '''
192 conf.Message('Checking for the use of global cstd... ')
193 ret = conf.TryLink(check_global_cstd_source, '.c')
198 def checkSelectArgType(conf):
199 ''' Adapted from autoconf '''
200 conf.Message('Checking for arg types for select... ')
201 for arg234 in ['fd_set *', 'int *', 'void *']:
202 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
203 for arg5 in ['struct timeval *', 'const struct timeval *']:
204 check_select_source = '''
205 #if HAVE_SYS_SELECT_H
206 # include <sys/select.h>
208 #if HAVE_SYS_SOCKET_H
209 # include <sys/socket.h>
211 extern int select (%s, %s, %s, %s, %s);
216 ''' % (arg1, arg234, arg234, arg234, arg5)
217 ret = conf.TryLink(check_select_source, '.c')
220 return (arg1, arg234, arg5)
221 conf.Result('no (use default)')
222 return ('int', 'int *', 'struct timeval *')
225 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
226 ''' look for boost libraries
228 lib_paths: try these paths for boost libraries
229 inc_paths: try these paths for boost headers
230 versions: supported boost versions
231 isDebug: if true, use debug libraries
233 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
234 libprefix = conf.env['LIBPREFIX']
235 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
241 for path in lib_paths:
242 conf.Log("Looking into %s\n" % path)
244 # get all the libs, then filter for the right library
245 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
246 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
248 conf.Log("Find boost libraries: %s\n" % files)
249 # runtime code includes s,g,y,d,p,n, where we should look for
250 # d,g,y for debug, s,p,n for release
254 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
257 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
258 if len(lib_files) == 0:
259 # use alternative libraries
261 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
262 if len(lib_files) > 0:
263 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
264 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
265 lib_names.append(name.split('.')[0])
266 conf.Log("Qualified libraries: %s\n" % lib_names)
268 conf.Log("No qualified library is found.\n")
270 if len(lib_names) == len(libs):
275 if len(lib_names) == 0:
276 conf.Log("No boost library is found\n")
278 conf.Log("Found boost libraries: %s\n" % lib_names)
280 return (None, None, None)
281 # check version number in boost/version.hpp
282 def isValidBoostDir(dir):
283 version_file = os.path.join(dir, 'boost', 'version.hpp')
284 if not os.path.isfile(version_file):
286 version_file_content = open(version_file).read()
287 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
288 return True in [x in version_file_content for x in version_strings]
289 # check for boost header file
290 for path in inc_paths:
291 conf.Log("Checking for inc path: %s\n" % path)
292 if isValidBoostDir(path):
295 else: # check path/boost_1_xx_x/boost
296 dirs = glob.glob(os.path.join(path, 'boost-*'))
297 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
298 conf.Log("Checing for sub directory: %s\n" % dirs[0])
304 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
305 return (lib_names, lib_path, inc_path)
308 return (None, None, None)
311 def checkCommand(conf, cmd):
312 ''' check the existence of a command
313 return full path to the command, or none
315 conf.Message('Checking for command %s...' % cmd)
317 conf.Result(res is not None)
322 ''' check the existence of nsis compiler, return the fullpath '''
323 conf.Message('Checking for nsis compiler...')
326 # If we can read the registry, get the NSIS command from it
328 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
330 val, tok = RegQueryValueEx(k,None)
331 ret = val + os.path.sep + 'makensis.exe'
332 if os.path.isfile(ret):
333 res = '"' + ret + '"'
337 pass # Couldn't find the key, just act like we can't read the registry
338 # Hope it's on the path
340 res = WhereIs('makensis.exe')
341 conf.Result(res is not None)
345 def checkLC_MESSAGES(conf):
346 ''' check the definition of LC_MESSAGES '''
347 check_LC_MESSAGES = '''
354 conf.Message('Checking for LC_MESSAGES in locale.h... ')
355 ret = conf.TryLink(check_LC_MESSAGES, '.c')
360 def checkIconvConst(conf):
361 ''' check the declaration of iconv '''
362 check_iconv_const = '''
364 // this declaration will fail when there already exists a non const char**
365 // version which returns size_t
366 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
371 conf.Message('Checking if the declaration of iconv needs const... ')
372 ret = conf.TryLink(check_iconv_const, '.c')
377 def checkSizeOfWChar(conf):
378 ''' check the size of wchar '''
379 check_sizeof_wchar = '''
380 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
386 conf.Message('Checking the size of wchar_t... ')
387 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
389 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
393 conf.Result(str(ret))
397 def createConfigFile(conf, config_file,
398 config_pre = '', config_post = '',
399 headers = [], functions = [], types = [], libs = [],
400 custom_tests = [], extra_items = []):
401 ''' create a configuration file, with options
402 config_file: which file to create
403 config_pre: first part of the config file
404 config_post: last part of the config file
405 headers: header files to check, in the form of a list of
406 ('file', 'HAVE_FILE', 'c'/'c++')
407 functions: functions to check, in the form of a list of
408 ('func', 'HAVE_func', 'include lines'/None)
409 types: types to check, in the form of a list of
410 ('type', 'HAVE_TYPE', 'includelines'/None)
411 libs: libraries to check, in the form of a list of
412 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
413 or any of the libs exists if 'lib' is a list of libs.
414 Optionally, user can provide another key LIB_NAME, that will
415 be set to the detected lib (or None otherwise).
416 custom_tests: extra tests to perform, in the form of a list of
417 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
418 If the last two are ignored, '#define key 1' '/*#undef key */'
420 extra_items: extra configuration lines, in the form of a list of
421 ('config', 'description')
423 The result of each test, as a dictioanry of
424 res['XXX'] = True/False
425 XXX are keys defined in each argument.
427 cont = config_pre + '\n'
429 # add to this string, in appropriate format
430 def configString(lines, desc=''):
432 if lines.strip() != '':
434 text += '/* ' + desc + ' */\n'
435 text += lines + '\n\n'
439 for header in headers:
440 description = "Define to 1 if you have the <%s> header file." % header[0]
441 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
442 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
443 result[header[1]] = 1
444 cont += configString('#define %s 1' % header[1], desc = description)
446 result[header[1]] = 0
447 cont += configString('/* #undef %s */' % header[1], desc = description)
449 for func in functions:
450 description = "Define to 1 if you have the `%s' function." % func[0]
451 if conf.CheckFunc(func[0], header=func[2]):
453 cont += configString('#define %s 1' % func[1], desc = description)
456 cont += configString('/* #undef %s */' % func[1], desc = description)
459 description = "Define to 1 if you have the `%s' type." % t[0]
460 if conf.CheckType(t[0], includes=t[2]):
462 cont += configString('#define %s 1' % t[1], desc = description)
465 cont += configString('/* #undef %s */' % t[1], desc = description)
468 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
469 if type(lib[0]) is type(''):
473 # check if any of the lib exists
475 # if user want the name of the lib detected
477 result[lib[2]] = None
479 if conf.CheckLib(ll):
483 cont += configString('#define %s 1' % lib[1], desc = description)
486 if not result[lib[1]]:
487 cont += configString('/* #undef %s */' % lib[1], desc = description)
489 for test in custom_tests:
493 cont += configString('#define %s 1' % test[1], desc = test[2])
495 cont += configString(test[3], desc = test[2])
499 cont += configString('/* #undef %s */' % test[1], desc = test[2])
501 cont += configString(test[4], desc = test[2])
502 # extra items (no key is returned)
503 for item in extra_items:
504 cont += configString(item[0], desc = item[1])
506 cont += '\n' + config_post + '\n'
508 writeToFile(config_file, cont)
512 def installCygwinLDScript(path):
513 ''' Install i386pe.x-no-rdata '''
514 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
515 script = open(ld_script, 'w')
516 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
518 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
519 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
521 OUTPUT_FORMAT(pei-i386)
522 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
523 ENTRY(_mainCRTStartup)
526 .text __image_base__ + __section_alignment__ :
533 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
534 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
535 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
536 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
538 /* ??? Why is .gcc_exc here? */
543 /* The Cygwin32 library uses a section to avoid copying certain data
544 on fork. This used to be named ".data". The linker used
545 to include this between __data_start__ and __data_end__, but that
546 breaks building the cygwin32 dll. Instead, we name the section
547 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
548 .data BLOCK(__section_alignment__) :
557 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
558 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
559 *(.rdata_runtime_pseudo_reloc)
560 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
561 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
563 *(.data_cygwin_nocopy)
565 .rdata BLOCK(__section_alignment__) :
568 .pdata BLOCK(__section_alignment__) :
572 .bss BLOCK(__section_alignment__) :
579 .edata BLOCK(__section_alignment__) :
590 .idata BLOCK(__section_alignment__) :
592 /* This cannot currently be handled with grouped sections.
593 See pe.em:sort_sections. */
596 /* These zeroes mark the end of the import list. */
597 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
603 .CRT BLOCK(__section_alignment__) :
605 ___crt_xc_start__ = . ;
606 *(SORT(.CRT$XC*)) /* C initialization */
607 ___crt_xc_end__ = . ;
608 ___crt_xi_start__ = . ;
609 *(SORT(.CRT$XI*)) /* C++ initialization */
610 ___crt_xi_end__ = . ;
611 ___crt_xl_start__ = . ;
612 *(SORT(.CRT$XL*)) /* TLS callbacks */
613 /* ___crt_xl_end__ is defined in the TLS Directory support code */
614 ___crt_xp_start__ = . ;
615 *(SORT(.CRT$XP*)) /* Pre-termination */
616 ___crt_xp_end__ = . ;
617 ___crt_xt_start__ = . ;
618 *(SORT(.CRT$XT*)) /* Termination */
619 ___crt_xt_end__ = . ;
621 .tls BLOCK(__section_alignment__) :
629 .endjunk BLOCK(__section_alignment__) :
631 /* end is deprecated, don't use it */
636 .rsrc BLOCK(__section_alignment__) :
641 .reloc BLOCK(__section_alignment__) :
645 .stab BLOCK(__section_alignment__) (NOLOAD) :
649 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
653 /* DWARF debug sections.
654 Symbols in the DWARF debugging sections are relative to the beginning
655 of the section. Unlike other targets that fake this by putting the
656 section VMA at 0, the PE format will not allow it. */
657 /* DWARF 1.1 and DWARF 2. */
658 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
662 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
667 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
669 *(.debug_info) *(.gnu.linkonce.wi.*)
671 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
675 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
679 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
683 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
687 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
691 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
695 /* SGI/MIPS DWARF 2 extensions. */
696 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
700 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
704 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
708 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
713 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
723 def installCygwinPostinstallScript(path):
724 ''' Install lyx.sh '''
725 postinstall_script = os.path.join(path, 'lyx.sh')
726 script = open(postinstall_script, 'w')
727 script.write(r'''#!/bin/sh
729 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
730 # if it is not already there.
731 if [ -f /etc/fonts/local.conf ]; then
732 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
733 if [ $? -ne 0 ]; then
734 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
735 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
736 fc-cache /usr/share/lyx/fonts
741 return(postinstall_script)
745 # these will be used under win32
751 # does not matter if it fails on other systems
756 def __init__(self, env, logfile, longarg, info):
757 # save the spawn system
759 self.logfile = logfile
760 # clear the logfile (it may not exist)
762 # this will overwrite existing content.
763 writeToFile(logfile, info, append=False)
765 self.longarg = longarg
766 # get hold of the old spawn? (necessary?)
767 self._spawn = env['SPAWN']
770 def spawn(self, sh, escape, cmd, args, spawnenv):
772 newargs = ' '.join(map(escape, args[1:]))
773 cmdline = cmd + " " + newargs
775 # if log is not empty, write to it
776 if self.logfile != '':
777 # this tend to be slow (?) but ensure correct output
778 # Note that cmdline may be long so I do not escape it
780 # since this is not an essential operation, proceed if things go wrong here.
781 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
783 print "Warning: can not write to log file ", self.logfile
785 # if the command is not too long, use the old
786 if not self.longarg or len(cmdline) < 8000:
787 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
789 sAttrs = win32security.SECURITY_ATTRIBUTES()
790 StartupInfo = win32process.STARTUPINFO()
792 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
793 # check for any special operating system commands
796 win32file.DeleteFile(arg)
799 # otherwise execute the command.
800 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
801 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
802 exit_code = win32process.GetExitCodeProcess(hProcess)
803 win32file.CloseHandle(hProcess);
804 win32file.CloseHandle(hThread);
808 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
809 ''' This function modify env and allow logging of
810 commands to a logfile. If the argument is too long
811 a win32 spawn will be used instead of the system one
814 # create a new spwn object
815 ls = loggedSpawn(env, logfile, longarg, info)
816 # replace the old SPAWN by the new function
817 env['SPAWN'] = ls.spawn