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 path3 = os.path.join(*path1[len(path2):]);
44 # replace all \ by / such that we get the same comments on Windows and *nix
45 path3 = path3.replace('\\', '/')
49 def writeToFile(filename, lines, append = False):
50 " utility function: write or append lines to filename "
51 # create directory if needed
52 dir = os.path.split(filename)[0]
53 if dir != '' and not os.path.isdir(dir):
56 file = open(filename, 'a')
58 file = open(filename, 'w')
63 def env_subst(target, source, env):
64 ''' subst variables in source by those in env, and output to target
65 source and target are scons File() objects
67 %key% (not key itself) is an indication of substitution
69 assert len(target) == 1
70 assert len(source) == 1
71 target_file = file(str(target[0]), "w")
72 source_file = file(str(source[0]), "r")
74 contents = source_file.read()
75 for k, v in env.items():
77 val = env.subst('$'+k)
78 # temporary fix for the \Resource backslash problem
79 val = val.replace('\\', '/')
80 # multi-line replacement
81 val = val.replace('\n',r'\\n\\\n')
82 contents = re.sub('@'+k+'@', val, contents)
85 target_file.write(contents + "\n")
87 #st = os.stat(str(source[0]))
88 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
91 def env_nsis(source, target, env, for_signature):
92 ''' Get nsis command line '''
93 def quoteIfSpaced(str):
95 return '"' + str + '"'
98 ret = env['NSIS'] + " /V1 "
99 if env.has_key('NSISFLAGS'):
100 for flag in env['NSISFLAGS']:
103 if env.has_key('NSISDEFINES'):
104 for d in env['NSISDEFINES']:
106 if env['NSISDEFINES'][d]:
107 ret += '=' + quoteIfSpaced(env['NSISDEFINES'][d])
110 if '-bundle.exe' in str(target[0]):
111 ret += '/DSETUPTYPE_BUNDLE=1 '
113 ret += quoteIfSpaced(str(s))
117 def env_toc(target, source, env):
118 '''Generate target from source files'''
119 # this is very tricky because we need to use installed lyx2lyx with
120 # correct lyx2lyx_version.py
121 sys.path.append(env['LYX2LYX_DEST'])
122 sys.path.append(env.Dir('$TOP_SRCDIR/lib/doc').abspath)
125 doc_toc.build_toc(str(target[0]), [file.abspath for file in source])
128 def env_cat(target, source, env):
129 '''Cat source > target. Avoid pipe to increase portability'''
130 output = open(env.File(target[0]).abspath, 'w')
132 input = open(env.File(src).abspath)
133 output.write(input.read())
138 def env_potfiles(target, source, env):
139 '''Build po/POTFILES.in'''
141 # grep -l '_(\".*\")' `find src \( -name '*.h' -o -name '*.cpp' -o -name '*.cpp.in' \) -print` | grep -v -e "src/support/Package.cpp$$" | sort | uniq
142 # is used under *nix but windows users have to do these all in python
143 target_file = open(str(target[0]), "w")
145 trans = re.compile('_\(".*"\)', re.M)
147 if str(file) not in potfiles and trans.search(open(str(file)).read()):
148 potfiles.append(relativePath(str(file), env.subst('$TOP_SRCDIR')))
150 print >> target_file, '\n'.join(potfiles)
154 def createResFromIcon(env, icon_file, rc_file):
155 ''' create a rc file with icon, and return res file (windows only) '''
157 rc_name = env.File(rc_file).abspath
158 dir = os.path.split(rc_name)[0]
159 if not os.path.isdir(dir):
161 rc = open(rc_name, 'w')
162 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
163 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
164 'packaging', 'icons', icon_file).replace('\\', '\\\\')
166 return env.RES(rc_name)
175 def checkPkgConfig(conf, version):
176 ''' Return false if pkg_config does not exist, or is too old '''
177 conf.Message('Checking for pkg-config...')
178 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
183 def checkPackage(conf, pkg):
184 ''' check if pkg is under the control of conf '''
185 conf.Message('Checking for package %s...' % pkg)
186 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
191 def checkMkdirOneArg(conf):
192 check_mkdir_one_arg_source = """
193 #include <sys/stat.h>
199 conf.Message('Checking for the number of args for mkdir... ')
200 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
201 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
202 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
210 def checkCXXGlobalCstd(conf):
211 ''' Checking the use of std::tolower or tolower '''
212 check_global_cstd_source = '''
220 conf.Message('Checking for the use of global cstd... ')
221 ret = conf.TryLink(check_global_cstd_source, '.c')
226 def checkSelectArgType(conf):
227 ''' Adapted from autoconf '''
228 conf.Message('Checking for arg types for select... ')
229 for arg234 in ['fd_set *', 'int *', 'void *']:
230 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
231 for arg5 in ['struct timeval *', 'const struct timeval *']:
232 check_select_source = '''
233 #if HAVE_SYS_SELECT_H
234 # include <sys/select.h>
236 #if HAVE_SYS_SOCKET_H
237 # include <sys/socket.h>
239 extern int select (%s, %s, %s, %s, %s);
244 ''' % (arg1, arg234, arg234, arg234, arg5)
245 ret = conf.TryLink(check_select_source, '.c')
248 return (arg1, arg234, arg5)
249 conf.Result('no (use default)')
250 return ('int', 'int *', 'struct timeval *')
253 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
254 ''' look for boost libraries
256 lib_paths: try these paths for boost libraries
257 inc_paths: try these paths for boost headers
258 versions: supported boost versions
259 isDebug: if true, use debug libraries
261 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
262 libprefix = conf.env['LIBPREFIX']
263 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
269 for path in lib_paths:
270 conf.Log("Looking into %s\n" % path)
272 # get all the libs, then filter for the right library
273 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
274 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
276 conf.Log("Find boost libraries: %s\n" % files)
277 # runtime code includes s,g,y,d,p,n, where we should look for
278 # d,g,y for debug, s,p,n for release
282 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
285 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
286 if len(lib_files) == 0:
287 # use alternative libraries
289 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
290 if len(lib_files) > 0:
291 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
292 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
293 lib_names.append(name.split('.')[0])
294 conf.Log("Qualified libraries: %s\n" % lib_names)
296 conf.Log("No qualified library is found.\n")
298 if len(lib_names) == len(libs):
303 if len(lib_names) == 0:
304 conf.Log("No boost library is found\n")
306 conf.Log("Found boost libraries: %s\n" % lib_names)
308 return (None, None, None)
309 # check version number in boost/version.hpp
310 def isValidBoostDir(dir):
311 version_file = os.path.join(dir, 'boost', 'version.hpp')
312 if not os.path.isfile(version_file):
314 version_file_content = open(version_file).read()
315 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
316 return True in [x in version_file_content for x in version_strings]
317 # check for boost header file
318 for path in inc_paths:
319 conf.Log("Checking for inc path: %s\n" % path)
320 if isValidBoostDir(path):
323 else: # check path/boost_1_xx_x/boost
324 dirs = glob.glob(os.path.join(path, 'boost-*'))
325 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
326 conf.Log("Checing for sub directory: %s\n" % dirs[0])
332 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
333 return (lib_names, lib_path, inc_path)
336 return (None, None, None)
339 def checkCommand(conf, cmd):
340 ''' check the existence of a command
341 return full path to the command, or none
343 conf.Message('Checking for command %s...' % cmd)
345 conf.Result(res is not None)
350 ''' check the existence of nsis compiler, return the fullpath '''
351 conf.Message('Checking for nsis compiler...')
354 # If we can read the registry, get the NSIS command from it
356 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
358 val, tok = RegQueryValueEx(k,None)
359 ret = val + os.path.sep + 'makensis.exe'
360 if os.path.isfile(ret):
361 res = '"' + ret + '"'
365 pass # Couldn't find the key, just act like we can't read the registry
366 # Hope it's on the path
368 res = WhereIs('makensis.exe')
369 conf.Result(res is not None)
373 def checkLC_MESSAGES(conf):
374 ''' check the definition of LC_MESSAGES '''
375 check_LC_MESSAGES = '''
382 conf.Message('Checking for LC_MESSAGES in locale.h... ')
383 ret = conf.TryLink(check_LC_MESSAGES, '.c')
388 def checkIconvConst(conf):
389 ''' check the declaration of iconv '''
390 check_iconv_const = '''
392 // this declaration will fail when there already exists a non const char**
393 // version which returns size_t
394 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
399 conf.Message('Checking if the declaration of iconv needs const... ')
400 ret = conf.TryLink(check_iconv_const, '.c')
405 def checkSizeOfWChar(conf):
406 ''' check the size of wchar '''
407 check_sizeof_wchar = '''
408 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
414 conf.Message('Checking the size of wchar_t... ')
415 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
417 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
421 conf.Result(str(ret))
425 def createConfigFile(conf, config_file,
426 config_pre = '', config_post = '',
427 headers = [], functions = [], types = [], libs = [],
428 custom_tests = [], extra_items = []):
429 ''' create a configuration file, with options
430 config_file: which file to create
431 config_pre: first part of the config file
432 config_post: last part of the config file
433 headers: header files to check, in the form of a list of
434 ('file', 'HAVE_FILE', 'c'/'c++')
435 functions: functions to check, in the form of a list of
436 ('func', 'HAVE_func', 'include lines'/None)
437 types: types to check, in the form of a list of
438 ('type', 'HAVE_TYPE', 'includelines'/None)
439 libs: libraries to check, in the form of a list of
440 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
441 or any of the libs exists if 'lib' is a list of libs.
442 Optionally, user can provide another key LIB_NAME, that will
443 be set to the detected lib (or None otherwise).
444 custom_tests: extra tests to perform, in the form of a list of
445 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
446 If the last two are ignored, '#define key 1' '/*#undef key */'
448 extra_items: extra configuration lines, in the form of a list of
449 ('config', 'description')
451 The result of each test, as a dictioanry of
452 res['XXX'] = True/False
453 XXX are keys defined in each argument.
455 cont = config_pre + '\n'
457 # add to this string, in appropriate format
458 def configString(lines, desc=''):
460 if lines.strip() != '':
462 text += '/* ' + desc + ' */\n'
463 text += lines + '\n\n'
467 for header in headers:
468 description = "Define to 1 if you have the <%s> header file." % header[0]
469 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
470 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
471 result[header[1]] = 1
472 cont += configString('#define %s 1' % header[1], desc = description)
474 result[header[1]] = 0
475 cont += configString('/* #undef %s */' % header[1], desc = description)
477 for func in functions:
478 description = "Define to 1 if you have the `%s' function." % func[0]
479 if conf.CheckFunc(func[0], header=func[2]):
481 cont += configString('#define %s 1' % func[1], desc = description)
484 cont += configString('/* #undef %s */' % func[1], desc = description)
487 description = "Define to 1 if you have the `%s' type." % t[0]
488 if conf.CheckType(t[0], includes=t[2]):
490 cont += configString('#define %s 1' % t[1], desc = description)
493 cont += configString('/* #undef %s */' % t[1], desc = description)
496 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
497 if type(lib[0]) is type(''):
501 # check if any of the lib exists
503 # if user want the name of the lib detected
505 result[lib[2]] = None
507 if conf.CheckLib(ll):
511 cont += configString('#define %s 1' % lib[1], desc = description)
514 if not result[lib[1]]:
515 cont += configString('/* #undef %s */' % lib[1], desc = description)
517 for test in custom_tests:
521 cont += configString('#define %s 1' % test[1], desc = test[2])
523 cont += configString(test[3], desc = test[2])
527 cont += configString('/* #undef %s */' % test[1], desc = test[2])
529 cont += configString(test[4], desc = test[2])
530 # extra items (no key is returned)
531 for item in extra_items:
532 cont += configString(item[0], desc = item[1])
534 cont += '\n' + config_post + '\n'
536 writeToFile(config_file, cont)
540 def installCygwinLDScript(path):
541 ''' Install i386pe.x-no-rdata '''
542 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
543 script = open(ld_script, 'w')
544 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
546 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
547 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
549 OUTPUT_FORMAT(pei-i386)
550 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
551 ENTRY(_mainCRTStartup)
554 .text __image_base__ + __section_alignment__ :
561 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
562 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
563 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
564 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
566 /* ??? Why is .gcc_exc here? */
571 /* The Cygwin32 library uses a section to avoid copying certain data
572 on fork. This used to be named ".data". The linker used
573 to include this between __data_start__ and __data_end__, but that
574 breaks building the cygwin32 dll. Instead, we name the section
575 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
576 .data BLOCK(__section_alignment__) :
585 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
586 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
587 *(.rdata_runtime_pseudo_reloc)
588 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
589 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
591 *(.data_cygwin_nocopy)
593 .rdata BLOCK(__section_alignment__) :
596 .pdata BLOCK(__section_alignment__) :
600 .bss BLOCK(__section_alignment__) :
607 .edata BLOCK(__section_alignment__) :
618 .idata BLOCK(__section_alignment__) :
620 /* This cannot currently be handled with grouped sections.
621 See pe.em:sort_sections. */
624 /* These zeroes mark the end of the import list. */
625 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
631 .CRT BLOCK(__section_alignment__) :
633 ___crt_xc_start__ = . ;
634 *(SORT(.CRT$XC*)) /* C initialization */
635 ___crt_xc_end__ = . ;
636 ___crt_xi_start__ = . ;
637 *(SORT(.CRT$XI*)) /* C++ initialization */
638 ___crt_xi_end__ = . ;
639 ___crt_xl_start__ = . ;
640 *(SORT(.CRT$XL*)) /* TLS callbacks */
641 /* ___crt_xl_end__ is defined in the TLS Directory support code */
642 ___crt_xp_start__ = . ;
643 *(SORT(.CRT$XP*)) /* Pre-termination */
644 ___crt_xp_end__ = . ;
645 ___crt_xt_start__ = . ;
646 *(SORT(.CRT$XT*)) /* Termination */
647 ___crt_xt_end__ = . ;
649 .tls BLOCK(__section_alignment__) :
657 .endjunk BLOCK(__section_alignment__) :
659 /* end is deprecated, don't use it */
664 .rsrc BLOCK(__section_alignment__) :
669 .reloc BLOCK(__section_alignment__) :
673 .stab BLOCK(__section_alignment__) (NOLOAD) :
677 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
681 /* DWARF debug sections.
682 Symbols in the DWARF debugging sections are relative to the beginning
683 of the section. Unlike other targets that fake this by putting the
684 section VMA at 0, the PE format will not allow it. */
685 /* DWARF 1.1 and DWARF 2. */
686 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
690 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
695 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
697 *(.debug_info) *(.gnu.linkonce.wi.*)
699 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
703 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
707 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
711 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
715 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
719 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
723 /* SGI/MIPS DWARF 2 extensions. */
724 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
728 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
732 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
736 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
741 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
751 def installCygwinPostinstallScript(path):
752 ''' Install lyx.sh '''
753 postinstall_script = os.path.join(path, 'lyx.sh')
754 script = open(postinstall_script, 'w')
755 script.write(r'''#!/bin/sh
757 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
758 # if it is not already there.
759 if [ -f /etc/fonts/local.conf ]; then
760 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
761 if [ $? -ne 0 ]; then
762 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
763 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
764 fc-cache /usr/share/lyx/fonts
769 return(postinstall_script)
773 # these will be used under win32
779 # does not matter if it fails on other systems
784 def __init__(self, env, logfile, longarg, info):
785 # save the spawn system
787 self.logfile = logfile
788 # clear the logfile (it may not exist)
790 # this will overwrite existing content.
791 writeToFile(logfile, info, append=False)
793 self.longarg = longarg
794 # get hold of the old spawn? (necessary?)
795 self._spawn = env['SPAWN']
798 def spawn(self, sh, escape, cmd, args, spawnenv):
800 newargs = ' '.join(map(escape, args[1:]))
801 cmdline = cmd + " " + newargs
803 # if log is not empty, write to it
804 if self.logfile != '':
805 # this tend to be slow (?) but ensure correct output
806 # Note that cmdline may be long so I do not escape it
808 # since this is not an essential operation, proceed if things go wrong here.
809 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
811 print "Warning: can not write to log file ", self.logfile
813 # if the command is not too long, use the old
814 if not self.longarg or len(cmdline) < 8000:
815 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
817 sAttrs = win32security.SECURITY_ATTRIBUTES()
818 StartupInfo = win32process.STARTUPINFO()
820 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
821 # check for any special operating system commands
824 win32file.DeleteFile(arg)
827 # otherwise execute the command.
828 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
829 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
830 exit_code = win32process.GetExitCodeProcess(hProcess)
831 win32file.CloseHandle(hProcess);
832 win32file.CloseHandle(hThread);
836 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
837 ''' This function modify env and allow logging of
838 commands to a logfile. If the argument is too long
839 a win32 spawn will be used instead of the system one
842 # create a new spwn object
843 ls = loggedSpawn(env, logfile, longarg, info)
844 # replace the old SPAWN by the new function
845 env['SPAWN'] = ls.spawn