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()
36 def relativePath(path, base):
37 '''return relative path from base, which is usually top source dir'''
38 # full pathname of path
39 path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
40 path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
41 if path1[:len(path2)] != path2:
42 print "Path %s is not under top source directory" % path
43 if len(path2) == len(path1):
45 path3 = os.path.join(*path1[len(path2):]);
46 # replace all \ by / such that we get the same comments on Windows and *nix
47 path3 = path3.replace('\\', '/')
51 def isSubDir(path, base):
52 '''Whether or not path is a subdirectory of base'''
53 path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
54 path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
55 return len(path2) <= len(path1) and path1[:len(path2)] == path2
58 def writeToFile(filename, lines, append = False):
59 " utility function: write or append lines to filename "
60 # create directory if needed
61 dir = os.path.split(filename)[0]
62 if dir != '' and not os.path.isdir(dir):
65 file = open(filename, 'a')
67 file = open(filename, 'w')
72 def env_subst(target, source, env):
73 ''' subst variables in source by those in env, and output to target
74 source and target are scons File() objects
76 %key% (not key itself) is an indication of substitution
78 assert len(target) == 1
79 assert len(source) == 1
80 target_file = file(str(target[0]), "w")
81 source_file = file(str(source[0]), "r")
83 contents = source_file.read()
84 for k, v in env.items():
86 val = env.subst('$'+k)
87 # temporary fix for the \Resource backslash problem
88 val = val.replace('\\', '/')
89 # multi-line replacement
90 val = val.replace('\n',r'\\n\\\n')
91 contents = re.sub('@'+k+'@', val, contents)
94 target_file.write(contents + "\n")
96 #st = os.stat(str(source[0]))
97 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
100 def env_nsis(source, target, env, for_signature):
101 ''' Get nsis command line '''
102 def quoteIfSpaced(str):
104 return '"' + str + '"'
107 ret = env['NSIS'] + " /V1 "
108 if env.has_key('NSISFLAGS'):
109 for flag in env['NSISFLAGS']:
112 if env.has_key('NSISDEFINES'):
113 for d in env['NSISDEFINES']:
115 if env['NSISDEFINES'][d]:
116 ret += '=' + quoteIfSpaced(env['NSISDEFINES'][d])
119 if '-bundle.exe' in str(target[0]):
120 ret += '/DSETUPTYPE_BUNDLE=1 '
122 ret += quoteIfSpaced(str(s))
126 def env_toc(target, source, env):
127 '''Generate target from source files'''
128 # this is very tricky because we need to use installed lyx2lyx with
129 # correct lyx2lyx_version.py
130 sys.path.append(env['LYX2LYX_DEST'])
131 sys.path.append(env.Dir('$TOP_SRCDIR/lib/doc').abspath)
134 doc_toc.build_toc(str(target[0]), [file.abspath for file in source])
137 def env_cat(target, source, env):
138 '''Cat source > target. Avoid pipe to increase portability'''
139 output = open(env.File(target[0]).abspath, 'w')
141 input = open(env.File(src).abspath)
142 output.write(input.read())
147 def env_potfiles(target, source, env):
148 '''Build po/POTFILES.in'''
150 # grep -l '_(\".*\")' `find src \( -name '*.h' -o -name '*.cpp' -o -name '*.cpp.in' \) -print` | grep -v -e "src/support/Package.cpp$$" | sort | uniq
151 # is used under *nix but windows users have to do these all in python
152 target_file = open(str(target[0]), "w")
154 trans = re.compile('_\(".*"\)', re.M)
156 if str(file) not in potfiles and trans.search(open(str(file)).read()):
157 potfiles.append(relativePath(str(file), env.subst('$TOP_SRCDIR')))
159 print >> target_file, '\n'.join(potfiles)
163 def createResFromIcon(env, icon_file, rc_file):
164 ''' create a rc file with icon, and return res file (windows only) '''
166 rc_name = env.File(rc_file).abspath
167 dir = os.path.split(rc_name)[0]
168 if not os.path.isdir(dir):
170 rc = open(rc_name, 'w')
171 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
172 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
173 'packaging', 'icons', icon_file).replace('\\', '\\\\')
175 return env.RES(rc_name)
184 def checkPkgConfig(conf, version):
185 ''' Return false if pkg_config does not exist, or is too old '''
186 conf.Message('Checking for pkg-config...')
187 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
192 def checkPackage(conf, pkg):
193 ''' check if pkg is under the control of conf '''
194 conf.Message('Checking for package %s...' % pkg)
195 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
200 def checkMkdirOneArg(conf):
201 check_mkdir_one_arg_source = """
202 #include <sys/stat.h>
208 conf.Message('Checking for the number of args for mkdir... ')
209 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
210 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
211 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
219 def checkCXXGlobalCstd(conf):
220 ''' Checking the use of std::tolower or tolower '''
221 check_global_cstd_source = '''
229 conf.Message('Checking for the use of global cstd... ')
230 ret = conf.TryLink(check_global_cstd_source, '.c')
235 def checkSelectArgType(conf):
236 ''' Adapted from autoconf '''
237 conf.Message('Checking for arg types for select... ')
238 for arg234 in ['fd_set *', 'int *', 'void *']:
239 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
240 for arg5 in ['struct timeval *', 'const struct timeval *']:
241 check_select_source = '''
242 #if HAVE_SYS_SELECT_H
243 # include <sys/select.h>
245 #if HAVE_SYS_SOCKET_H
246 # include <sys/socket.h>
248 extern int select (%s, %s, %s, %s, %s);
253 ''' % (arg1, arg234, arg234, arg234, arg5)
254 ret = conf.TryLink(check_select_source, '.c')
257 return (arg1, arg234, arg5)
258 conf.Result('no (use default)')
259 return ('int', 'int *', 'struct timeval *')
262 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
263 ''' look for boost libraries
265 lib_paths: try these paths for boost libraries
266 inc_paths: try these paths for boost headers
267 versions: supported boost versions
268 isDebug: if true, use debug libraries
270 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
271 libprefix = conf.env['LIBPREFIX']
272 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
278 for path in lib_paths:
279 conf.Log("Looking into %s\n" % path)
281 # get all the libs, then filter for the right library
282 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
283 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
285 conf.Log("Find boost libraries: %s\n" % files)
286 # runtime code includes s,g,y,d,p,n, where we should look for
287 # d,g,y for debug, s,p,n for release
291 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
294 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
295 if len(lib_files) == 0:
296 # use alternative libraries
298 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
299 if len(lib_files) > 0:
300 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
301 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
302 lib_names.append(name.split('.')[0])
303 conf.Log("Qualified libraries: %s\n" % lib_names)
305 conf.Log("No qualified library is found.\n")
307 if len(lib_names) == len(libs):
312 if len(lib_names) == 0:
313 conf.Log("No boost library is found\n")
315 conf.Log("Found boost libraries: %s\n" % lib_names)
317 return (None, None, None)
318 # check version number in boost/version.hpp
319 def isValidBoostDir(dir):
320 version_file = os.path.join(dir, 'boost', 'version.hpp')
321 if not os.path.isfile(version_file):
323 version_file_content = open(version_file).read()
324 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
325 return True in [x in version_file_content for x in version_strings]
326 # check for boost header file
327 for path in inc_paths:
328 conf.Log("Checking for inc path: %s\n" % path)
329 if isValidBoostDir(path):
332 else: # check path/boost_1_xx_x/boost
333 dirs = glob.glob(os.path.join(path, 'boost-*'))
334 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
335 conf.Log("Checing for sub directory: %s\n" % dirs[0])
341 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
342 return (lib_names, lib_path, inc_path)
345 return (None, None, None)
348 def checkCommand(conf, cmd):
349 ''' check the existence of a command
350 return full path to the command, or none
352 conf.Message('Checking for command %s...' % cmd)
354 conf.Result(res is not None)
359 ''' check the existence of nsis compiler, return the fullpath '''
360 conf.Message('Checking for nsis compiler...')
363 # If we can read the registry, get the NSIS command from it
365 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
367 val, tok = RegQueryValueEx(k,None)
368 ret = val + os.path.sep + 'makensis.exe'
369 if os.path.isfile(ret):
370 res = '"' + ret + '"'
374 pass # Couldn't find the key, just act like we can't read the registry
375 # Hope it's on the path
377 res = WhereIs('makensis.exe')
378 conf.Result(res is not None)
382 def checkLC_MESSAGES(conf):
383 ''' check the definition of LC_MESSAGES '''
384 check_LC_MESSAGES = '''
391 conf.Message('Checking for LC_MESSAGES in locale.h... ')
392 ret = conf.TryLink(check_LC_MESSAGES, '.c')
397 def checkIconvConst(conf):
398 ''' check the declaration of iconv '''
399 check_iconv_const = '''
401 // this declaration will fail when there already exists a non const char**
402 // version which returns size_t
403 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
408 conf.Message('Checking if the declaration of iconv needs const... ')
409 ret = conf.TryLink(check_iconv_const, '.c')
414 def checkSizeOfWChar(conf):
415 ''' check the size of wchar '''
416 check_sizeof_wchar = '''
417 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
423 conf.Message('Checking the size of wchar_t... ')
424 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
426 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
430 conf.Result(str(ret))
434 def createConfigFile(conf, config_file,
435 config_pre = '', config_post = '',
436 headers = [], functions = [], types = [], libs = [],
437 custom_tests = [], extra_items = []):
438 ''' create a configuration file, with options
439 config_file: which file to create
440 config_pre: first part of the config file
441 config_post: last part of the config file
442 headers: header files to check, in the form of a list of
443 ('file', 'HAVE_FILE', 'c'/'c++')
444 functions: functions to check, in the form of a list of
445 ('func', 'HAVE_func', 'include lines'/None)
446 types: types to check, in the form of a list of
447 ('type', 'HAVE_TYPE', 'includelines'/None)
448 libs: libraries to check, in the form of a list of
449 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
450 or any of the libs exists if 'lib' is a list of libs.
451 Optionally, user can provide another key LIB_NAME, that will
452 be set to the detected lib (or None otherwise).
453 custom_tests: extra tests to perform, in the form of a list of
454 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
455 If the last two are ignored, '#define key 1' '/*#undef key */'
457 extra_items: extra configuration lines, in the form of a list of
458 ('config', 'description')
460 The result of each test, as a dictioanry of
461 res['XXX'] = True/False
462 XXX are keys defined in each argument.
464 cont = config_pre + '\n'
466 # add to this string, in appropriate format
467 def configString(lines, desc=''):
469 if lines.strip() != '':
471 text += '/* ' + desc + ' */\n'
472 text += lines + '\n\n'
476 for header in headers:
477 description = "Define to 1 if you have the <%s> header file." % header[0]
478 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
479 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
480 result[header[1]] = 1
481 cont += configString('#define %s 1' % header[1], desc = description)
483 result[header[1]] = 0
484 cont += configString('/* #undef %s */' % header[1], desc = description)
486 for func in functions:
487 description = "Define to 1 if you have the `%s' function." % func[0]
488 if conf.CheckFunc(func[0], header=func[2]):
490 cont += configString('#define %s 1' % func[1], desc = description)
493 cont += configString('/* #undef %s */' % func[1], desc = description)
496 description = "Define to 1 if you have the `%s' type." % t[0]
497 if conf.CheckType(t[0], includes=t[2]):
499 cont += configString('#define %s 1' % t[1], desc = description)
502 cont += configString('/* #undef %s */' % t[1], desc = description)
505 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
506 if type(lib[0]) is type(''):
510 # check if any of the lib exists
512 # if user want the name of the lib detected
514 result[lib[2]] = None
516 if conf.CheckLib(ll):
520 cont += configString('#define %s 1' % lib[1], desc = description)
523 if not result[lib[1]]:
524 cont += configString('/* #undef %s */' % lib[1], desc = description)
526 for test in custom_tests:
530 cont += configString('#define %s 1' % test[1], desc = test[2])
532 cont += configString(test[3], desc = test[2])
536 cont += configString('/* #undef %s */' % test[1], desc = test[2])
538 cont += configString(test[4], desc = test[2])
539 # extra items (no key is returned)
540 for item in extra_items:
541 cont += configString(item[0], desc = item[1])
543 cont += '\n' + config_post + '\n'
545 writeToFile(config_file, cont)
549 def installCygwinLDScript(path):
550 ''' Install i386pe.x-no-rdata '''
551 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
552 script = open(ld_script, 'w')
553 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
555 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
556 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
558 OUTPUT_FORMAT(pei-i386)
559 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
560 ENTRY(_mainCRTStartup)
563 .text __image_base__ + __section_alignment__ :
570 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
571 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
572 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
573 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
575 /* ??? Why is .gcc_exc here? */
580 /* The Cygwin32 library uses a section to avoid copying certain data
581 on fork. This used to be named ".data". The linker used
582 to include this between __data_start__ and __data_end__, but that
583 breaks building the cygwin32 dll. Instead, we name the section
584 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
585 .data BLOCK(__section_alignment__) :
594 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
595 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
596 *(.rdata_runtime_pseudo_reloc)
597 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
598 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
600 *(.data_cygwin_nocopy)
602 .rdata BLOCK(__section_alignment__) :
605 .pdata BLOCK(__section_alignment__) :
609 .bss BLOCK(__section_alignment__) :
616 .edata BLOCK(__section_alignment__) :
627 .idata BLOCK(__section_alignment__) :
629 /* This cannot currently be handled with grouped sections.
630 See pe.em:sort_sections. */
633 /* These zeroes mark the end of the import list. */
634 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
640 .CRT BLOCK(__section_alignment__) :
642 ___crt_xc_start__ = . ;
643 *(SORT(.CRT$XC*)) /* C initialization */
644 ___crt_xc_end__ = . ;
645 ___crt_xi_start__ = . ;
646 *(SORT(.CRT$XI*)) /* C++ initialization */
647 ___crt_xi_end__ = . ;
648 ___crt_xl_start__ = . ;
649 *(SORT(.CRT$XL*)) /* TLS callbacks */
650 /* ___crt_xl_end__ is defined in the TLS Directory support code */
651 ___crt_xp_start__ = . ;
652 *(SORT(.CRT$XP*)) /* Pre-termination */
653 ___crt_xp_end__ = . ;
654 ___crt_xt_start__ = . ;
655 *(SORT(.CRT$XT*)) /* Termination */
656 ___crt_xt_end__ = . ;
658 .tls BLOCK(__section_alignment__) :
666 .endjunk BLOCK(__section_alignment__) :
668 /* end is deprecated, don't use it */
673 .rsrc BLOCK(__section_alignment__) :
678 .reloc BLOCK(__section_alignment__) :
682 .stab BLOCK(__section_alignment__) (NOLOAD) :
686 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
690 /* DWARF debug sections.
691 Symbols in the DWARF debugging sections are relative to the beginning
692 of the section. Unlike other targets that fake this by putting the
693 section VMA at 0, the PE format will not allow it. */
694 /* DWARF 1.1 and DWARF 2. */
695 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
699 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
704 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
706 *(.debug_info) *(.gnu.linkonce.wi.*)
708 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
712 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
716 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
720 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
724 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
728 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
732 /* SGI/MIPS DWARF 2 extensions. */
733 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
737 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
741 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
745 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
750 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
760 def installCygwinPostinstallScript(path):
761 ''' Install lyx.sh '''
762 postinstall_script = os.path.join(path, 'lyx.sh')
763 script = open(postinstall_script, 'w')
764 script.write(r'''#!/bin/sh
766 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
767 # if it is not already there.
768 if [ -f /etc/fonts/local.conf ]; then
769 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
770 if [ $? -ne 0 ]; then
771 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
772 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
773 fc-cache /usr/share/lyx/fonts
778 return(postinstall_script)
782 # these will be used under win32
788 # does not matter if it fails on other systems
793 def __init__(self, env, logfile, longarg, info):
794 # save the spawn system
796 self.logfile = logfile
797 # clear the logfile (it may not exist)
799 # this will overwrite existing content.
800 writeToFile(logfile, info, append=False)
802 self.longarg = longarg
803 # get hold of the old spawn? (necessary?)
804 self._spawn = env['SPAWN']
807 def spawn(self, sh, escape, cmd, args, spawnenv):
809 newargs = ' '.join(map(escape, args[1:]))
810 cmdline = cmd + " " + newargs
812 # if log is not empty, write to it
813 if self.logfile != '':
814 # this tend to be slow (?) but ensure correct output
815 # Note that cmdline may be long so I do not escape it
817 # since this is not an essential operation, proceed if things go wrong here.
818 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
820 print "Warning: can not write to log file ", self.logfile
822 # if the command is not too long, use the old
823 if not self.longarg or len(cmdline) < 8000:
824 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
826 sAttrs = win32security.SECURITY_ATTRIBUTES()
827 StartupInfo = win32process.STARTUPINFO()
829 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
830 # check for any special operating system commands
833 win32file.DeleteFile(arg)
836 # otherwise execute the command.
837 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
838 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
839 exit_code = win32process.GetExitCodeProcess(hProcess)
840 win32file.CloseHandle(hProcess);
841 win32file.CloseHandle(hThread);
845 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
846 ''' This function modify env and allow logging of
847 commands to a logfile. If the argument is too long
848 a win32 spawn will be used instead of the system one
851 # create a new spwn object
852 ls = loggedSpawn(env, logfile, longarg, info)
853 # replace the old SPAWN by the new function
854 env['SPAWN'] = ls.spawn