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 relativePath(env, path):
117 '''return relative path from top source dir'''
118 # full pathname of path
119 path1 = os.path.normpath(env.File(path).abspath).split(os.sep)
120 path2 = os.path.normpath(env.Dir('$TOP_SRCDIR').abspath).split(os.sep)
121 if path1[:len(path2)] != path2:
122 print "Path %s is not under top source directory" % path
123 return os.path.join(*path1[len(path2):])
126 def env_language_l10n(target, source, env):
127 '''Generate pot file from lib/language'''
128 input = open(env.File(source[0]).abspath)
129 output = open(env.File(target[0]).abspath, 'w')
130 for lineno, line in enumerate(input.readlines()):
133 items = line.split('"')
136 print 'Warning: this line looks strange:'
139 # afrikaans afrikaans "Afrikaans" false iso8859-15 af_ZA ""
144 print >> output, '#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % (relativePath(env, source[0]), lineno+1, items[1])
149 def createResFromIcon(env, icon_file, rc_file):
150 ''' create a rc file with icon, and return res file (windows only) '''
152 rc_name = env.File(rc_file).abspath
153 dir = os.path.split(rc_name)[0]
154 if not os.path.isdir(dir):
156 rc = open(rc_name, 'w')
157 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
158 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
159 'packaging', 'icons', icon_file).replace('\\', '\\\\')
161 return env.RES(rc_name)
170 def checkPkgConfig(conf, version):
171 ''' Return false if pkg_config does not exist, or is too old '''
172 conf.Message('Checking for pkg-config...')
173 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
178 def checkPackage(conf, pkg):
179 ''' check if pkg is under the control of conf '''
180 conf.Message('Checking for package %s...' % pkg)
181 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
186 def checkMkdirOneArg(conf):
187 check_mkdir_one_arg_source = """
188 #include <sys/stat.h>
194 conf.Message('Checking for the number of args for mkdir... ')
195 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
196 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
197 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
205 def checkCXXGlobalCstd(conf):
206 ''' Checking the use of std::tolower or tolower '''
207 check_global_cstd_source = '''
215 conf.Message('Checking for the use of global cstd... ')
216 ret = conf.TryLink(check_global_cstd_source, '.c')
221 def checkSelectArgType(conf):
222 ''' Adapted from autoconf '''
223 conf.Message('Checking for arg types for select... ')
224 for arg234 in ['fd_set *', 'int *', 'void *']:
225 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
226 for arg5 in ['struct timeval *', 'const struct timeval *']:
227 check_select_source = '''
228 #if HAVE_SYS_SELECT_H
229 # include <sys/select.h>
231 #if HAVE_SYS_SOCKET_H
232 # include <sys/socket.h>
234 extern int select (%s, %s, %s, %s, %s);
239 ''' % (arg1, arg234, arg234, arg234, arg5)
240 ret = conf.TryLink(check_select_source, '.c')
243 return (arg1, arg234, arg5)
244 conf.Result('no (use default)')
245 return ('int', 'int *', 'struct timeval *')
248 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
249 ''' look for boost libraries
251 lib_paths: try these paths for boost libraries
252 inc_paths: try these paths for boost headers
253 versions: supported boost versions
254 isDebug: if true, use debug libraries
256 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
257 libprefix = conf.env['LIBPREFIX']
258 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
264 for path in lib_paths:
265 conf.Log("Looking into %s\n" % path)
267 # get all the libs, then filter for the right library
268 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
269 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
271 conf.Log("Find boost libraries: %s\n" % files)
272 # runtime code includes s,g,y,d,p,n, where we should look for
273 # d,g,y for debug, s,p,n for release
277 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
280 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
281 if len(lib_files) == 0:
282 # use alternative libraries
284 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
285 if len(lib_files) > 0:
286 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
287 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
288 lib_names.append(name.split('.')[0])
289 conf.Log("Qualified libraries: %s\n" % lib_names)
291 conf.Log("No qualified library is found.\n")
293 if len(lib_names) == len(libs):
298 if len(lib_names) == 0:
299 conf.Log("No boost library is found\n")
301 conf.Log("Found boost libraries: %s\n" % lib_names)
303 return (None, None, None)
304 # check version number in boost/version.hpp
305 def isValidBoostDir(dir):
306 version_file = os.path.join(dir, 'boost', 'version.hpp')
307 if not os.path.isfile(version_file):
309 version_file_content = open(version_file).read()
310 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
311 return True in [x in version_file_content for x in version_strings]
312 # check for boost header file
313 for path in inc_paths:
314 conf.Log("Checking for inc path: %s\n" % path)
315 if isValidBoostDir(path):
318 else: # check path/boost_1_xx_x/boost
319 dirs = glob.glob(os.path.join(path, 'boost-*'))
320 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
321 conf.Log("Checing for sub directory: %s\n" % dirs[0])
327 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
328 return (lib_names, lib_path, inc_path)
331 return (None, None, None)
334 def checkCommand(conf, cmd):
335 ''' check the existence of a command
336 return full path to the command, or none
338 conf.Message('Checking for command %s...' % cmd)
340 conf.Result(res is not None)
345 ''' check the existence of nsis compiler, return the fullpath '''
346 conf.Message('Checking for nsis compiler...')
349 # If we can read the registry, get the NSIS command from it
351 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
353 val, tok = RegQueryValueEx(k,None)
354 ret = val + os.path.sep + 'makensis.exe'
355 if os.path.isfile(ret):
356 res = '"' + ret + '"'
360 pass # Couldn't find the key, just act like we can't read the registry
361 # Hope it's on the path
363 res = WhereIs('makensis.exe')
364 conf.Result(res is not None)
368 def checkLC_MESSAGES(conf):
369 ''' check the definition of LC_MESSAGES '''
370 check_LC_MESSAGES = '''
377 conf.Message('Checking for LC_MESSAGES in locale.h... ')
378 ret = conf.TryLink(check_LC_MESSAGES, '.c')
383 def checkIconvConst(conf):
384 ''' check the declaration of iconv '''
385 check_iconv_const = '''
387 // this declaration will fail when there already exists a non const char**
388 // version which returns size_t
389 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
394 conf.Message('Checking if the declaration of iconv needs const... ')
395 ret = conf.TryLink(check_iconv_const, '.c')
400 def checkSizeOfWChar(conf):
401 ''' check the size of wchar '''
402 check_sizeof_wchar = '''
403 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
409 conf.Message('Checking the size of wchar_t... ')
410 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
412 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
416 conf.Result(str(ret))
420 def createConfigFile(conf, config_file,
421 config_pre = '', config_post = '',
422 headers = [], functions = [], types = [], libs = [],
423 custom_tests = [], extra_items = []):
424 ''' create a configuration file, with options
425 config_file: which file to create
426 config_pre: first part of the config file
427 config_post: last part of the config file
428 headers: header files to check, in the form of a list of
429 ('file', 'HAVE_FILE', 'c'/'c++')
430 functions: functions to check, in the form of a list of
431 ('func', 'HAVE_func', 'include lines'/None)
432 types: types to check, in the form of a list of
433 ('type', 'HAVE_TYPE', 'includelines'/None)
434 libs: libraries to check, in the form of a list of
435 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
436 or any of the libs exists if 'lib' is a list of libs.
437 Optionally, user can provide another key LIB_NAME, that will
438 be set to the detected lib (or None otherwise).
439 custom_tests: extra tests to perform, in the form of a list of
440 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
441 If the last two are ignored, '#define key 1' '/*#undef key */'
443 extra_items: extra configuration lines, in the form of a list of
444 ('config', 'description')
446 The result of each test, as a dictioanry of
447 res['XXX'] = True/False
448 XXX are keys defined in each argument.
450 cont = config_pre + '\n'
452 # add to this string, in appropriate format
453 def configString(lines, desc=''):
455 if lines.strip() != '':
457 text += '/* ' + desc + ' */\n'
458 text += lines + '\n\n'
462 for header in headers:
463 description = "Define to 1 if you have the <%s> header file." % header[0]
464 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
465 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
466 result[header[1]] = 1
467 cont += configString('#define %s 1' % header[1], desc = description)
469 result[header[1]] = 0
470 cont += configString('/* #undef %s */' % header[1], desc = description)
472 for func in functions:
473 description = "Define to 1 if you have the `%s' function." % func[0]
474 if conf.CheckFunc(func[0], header=func[2]):
476 cont += configString('#define %s 1' % func[1], desc = description)
479 cont += configString('/* #undef %s */' % func[1], desc = description)
482 description = "Define to 1 if you have the `%s' type." % t[0]
483 if conf.CheckType(t[0], includes=t[2]):
485 cont += configString('#define %s 1' % t[1], desc = description)
488 cont += configString('/* #undef %s */' % t[1], desc = description)
491 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
492 if type(lib[0]) is type(''):
496 # check if any of the lib exists
498 # if user want the name of the lib detected
500 result[lib[2]] = None
502 if conf.CheckLib(ll):
506 cont += configString('#define %s 1' % lib[1], desc = description)
509 if not result[lib[1]]:
510 cont += configString('/* #undef %s */' % lib[1], desc = description)
512 for test in custom_tests:
516 cont += configString('#define %s 1' % test[1], desc = test[2])
518 cont += configString(test[3], desc = test[2])
522 cont += configString('/* #undef %s */' % test[1], desc = test[2])
524 cont += configString(test[4], desc = test[2])
525 # extra items (no key is returned)
526 for item in extra_items:
527 cont += configString(item[0], desc = item[1])
529 cont += '\n' + config_post + '\n'
531 writeToFile(config_file, cont)
535 def installCygwinLDScript(path):
536 ''' Install i386pe.x-no-rdata '''
537 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
538 script = open(ld_script, 'w')
539 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
541 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
542 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
544 OUTPUT_FORMAT(pei-i386)
545 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
546 ENTRY(_mainCRTStartup)
549 .text __image_base__ + __section_alignment__ :
556 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
557 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
558 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
559 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
561 /* ??? Why is .gcc_exc here? */
566 /* The Cygwin32 library uses a section to avoid copying certain data
567 on fork. This used to be named ".data". The linker used
568 to include this between __data_start__ and __data_end__, but that
569 breaks building the cygwin32 dll. Instead, we name the section
570 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
571 .data BLOCK(__section_alignment__) :
580 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
581 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
582 *(.rdata_runtime_pseudo_reloc)
583 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
584 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
586 *(.data_cygwin_nocopy)
588 .rdata BLOCK(__section_alignment__) :
591 .pdata BLOCK(__section_alignment__) :
595 .bss BLOCK(__section_alignment__) :
602 .edata BLOCK(__section_alignment__) :
613 .idata BLOCK(__section_alignment__) :
615 /* This cannot currently be handled with grouped sections.
616 See pe.em:sort_sections. */
619 /* These zeroes mark the end of the import list. */
620 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
626 .CRT BLOCK(__section_alignment__) :
628 ___crt_xc_start__ = . ;
629 *(SORT(.CRT$XC*)) /* C initialization */
630 ___crt_xc_end__ = . ;
631 ___crt_xi_start__ = . ;
632 *(SORT(.CRT$XI*)) /* C++ initialization */
633 ___crt_xi_end__ = . ;
634 ___crt_xl_start__ = . ;
635 *(SORT(.CRT$XL*)) /* TLS callbacks */
636 /* ___crt_xl_end__ is defined in the TLS Directory support code */
637 ___crt_xp_start__ = . ;
638 *(SORT(.CRT$XP*)) /* Pre-termination */
639 ___crt_xp_end__ = . ;
640 ___crt_xt_start__ = . ;
641 *(SORT(.CRT$XT*)) /* Termination */
642 ___crt_xt_end__ = . ;
644 .tls BLOCK(__section_alignment__) :
652 .endjunk BLOCK(__section_alignment__) :
654 /* end is deprecated, don't use it */
659 .rsrc BLOCK(__section_alignment__) :
664 .reloc BLOCK(__section_alignment__) :
668 .stab BLOCK(__section_alignment__) (NOLOAD) :
672 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
676 /* DWARF debug sections.
677 Symbols in the DWARF debugging sections are relative to the beginning
678 of the section. Unlike other targets that fake this by putting the
679 section VMA at 0, the PE format will not allow it. */
680 /* DWARF 1.1 and DWARF 2. */
681 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
685 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
690 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
692 *(.debug_info) *(.gnu.linkonce.wi.*)
694 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
698 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
702 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
706 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
710 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
714 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
718 /* SGI/MIPS DWARF 2 extensions. */
719 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
723 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
727 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
731 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
736 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
746 def installCygwinPostinstallScript(path):
747 ''' Install lyx.sh '''
748 postinstall_script = os.path.join(path, 'lyx.sh')
749 script = open(postinstall_script, 'w')
750 script.write(r'''#!/bin/sh
752 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
753 # if it is not already there.
754 if [ -f /etc/fonts/local.conf ]; then
755 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
756 if [ $? -ne 0 ]; then
757 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
758 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
759 fc-cache /usr/share/lyx/fonts
764 return(postinstall_script)
768 # these will be used under win32
774 # does not matter if it fails on other systems
779 def __init__(self, env, logfile, longarg, info):
780 # save the spawn system
782 self.logfile = logfile
783 # clear the logfile (it may not exist)
785 # this will overwrite existing content.
786 writeToFile(logfile, info, append=False)
788 self.longarg = longarg
789 # get hold of the old spawn? (necessary?)
790 self._spawn = env['SPAWN']
793 def spawn(self, sh, escape, cmd, args, spawnenv):
795 newargs = ' '.join(map(escape, args[1:]))
796 cmdline = cmd + " " + newargs
798 # if log is not empty, write to it
799 if self.logfile != '':
800 # this tend to be slow (?) but ensure correct output
801 # Note that cmdline may be long so I do not escape it
803 # since this is not an essential operation, proceed if things go wrong here.
804 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
806 print "Warning: can not write to log file ", self.logfile
808 # if the command is not too long, use the old
809 if not self.longarg or len(cmdline) < 8000:
810 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
812 sAttrs = win32security.SECURITY_ATTRIBUTES()
813 StartupInfo = win32process.STARTUPINFO()
815 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
816 # check for any special operating system commands
819 win32file.DeleteFile(arg)
822 # otherwise execute the command.
823 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
824 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
825 exit_code = win32process.GetExitCodeProcess(hProcess)
826 win32file.CloseHandle(hProcess);
827 win32file.CloseHandle(hThread);
831 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
832 ''' This function modify env and allow logging of
833 commands to a logfile. If the argument is too long
834 a win32 spawn will be used instead of the system one
837 # create a new spwn object
838 ls = loggedSpawn(env, logfile, longarg, info)
839 # replace the old SPAWN by the new function
840 env['SPAWN'] = ls.spawn