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'] + " "
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(toString(env['NSISDEFINES'][d],env))
98 ret += quoteIfSpaced(str(s))
104 def createResFromIcon(env, icon_file, rc_file):
105 ''' create a rc file with icon, and return res file (windows only) '''
107 rc_name = env.File(rc_file).abspath
108 dir = os.path.split(rc_name)[0]
109 if not os.path.isdir(dir):
111 rc = open(rc_name, 'w')
112 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
113 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
114 'packaging', 'icons', icon_file).replace('\\', '\\\\')
116 return env.RES(rc_name)
125 def checkPkgConfig(conf, version):
126 ''' Return false if pkg_config does not exist, or is too old '''
127 conf.Message('Checking for pkg-config...')
128 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
133 def checkPackage(conf, pkg):
134 ''' check if pkg is under the control of conf '''
135 conf.Message('Checking for package %s...' % pkg)
136 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
141 def checkMkdirOneArg(conf):
142 check_mkdir_one_arg_source = """
143 #include <sys/stat.h>
149 conf.Message('Checking for the number of args for mkdir... ')
150 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
151 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
152 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
160 def checkCXXGlobalCstd(conf):
161 ''' Checking the use of std::tolower or tolower '''
162 check_global_cstd_source = '''
170 conf.Message('Checking for the use of global cstd... ')
171 ret = conf.TryLink(check_global_cstd_source, '.c')
176 def checkSelectArgType(conf):
177 ''' Adapted from autoconf '''
178 conf.Message('Checking for arg types for select... ')
179 for arg234 in ['fd_set *', 'int *', 'void *']:
180 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
181 for arg5 in ['struct timeval *', 'const struct timeval *']:
182 check_select_source = '''
183 #if HAVE_SYS_SELECT_H
184 # include <sys/select.h>
186 #if HAVE_SYS_SOCKET_H
187 # include <sys/socket.h>
189 extern int select (%s, %s, %s, %s, %s);
194 ''' % (arg1, arg234, arg234, arg234, arg5)
195 ret = conf.TryLink(check_select_source, '.c')
198 return (arg1, arg234, arg5)
199 conf.Result('no (use default)')
200 return ('int', 'int *', 'struct timeval *')
203 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
204 ''' look for boost libraries
206 lib_paths: try these paths for boost libraries
207 inc_paths: try these paths for boost headers
208 versions: supported boost versions
209 isDebug: if true, use debug libraries
211 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
212 libprefix = conf.env['LIBPREFIX']
213 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
219 for path in lib_paths:
220 conf.Log("Looking into %s\n" % path)
222 # get all the libs, then filter for the right library
223 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
224 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
226 conf.Log("Find boost libraries: %s\n" % files)
227 # runtime code includes s,g,y,d,p,n, where we should look for
228 # d,g,y for debug, s,p,n for release
232 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
235 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
236 if len(lib_files) == 0:
237 # use alternative libraries
239 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
240 if len(lib_files) > 0:
241 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
242 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
243 lib_names.append(name.split('.')[0])
244 conf.Log("Qualified libraries: %s\n" % lib_names)
246 conf.Log("No qualified library is found.\n")
248 if len(lib_names) == len(libs):
253 if len(lib_names) == 0:
254 conf.Log("No boost library is found\n")
256 conf.Log("Found boost libraries: %s\n" % lib_names)
258 return (None, None, None)
259 # check version number in boost/version.hpp
260 def isValidBoostDir(dir):
261 version_file = os.path.join(dir, 'boost', 'version.hpp')
262 if not os.path.isfile(version_file):
264 version_file_content = open(version_file).read()
265 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
266 return True in [x in version_file_content for x in version_strings]
267 # check for boost header file
268 for path in inc_paths:
269 conf.Log("Checking for inc path: %s\n" % path)
270 if isValidBoostDir(path):
273 else: # check path/boost_1_xx_x/boost
274 dirs = glob.glob(os.path.join(path, 'boost-*'))
275 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
276 conf.Log("Checing for sub directory: %s\n" % dirs[0])
282 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
283 return (lib_names, lib_path, inc_path)
286 return (None, None, None)
289 def checkCommand(conf, cmd):
290 ''' check the existence of a command
291 return full path to the command, or none
293 conf.Message('Checking for command %s...' % cmd)
295 conf.Result(res is not None)
300 ''' check the existence of nsis compiler, return the fullpath '''
301 conf.Message('Checking for nsis compiler...')
304 # If we can read the registry, get the NSIS command from it
306 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
308 val, tok = RegQueryValueEx(k,None)
309 ret = val + os.path.sep + 'makensis.exe'
310 if os.path.isfile(ret):
311 res = '"' + ret + '"'
315 pass # Couldn't find the key, just act like we can't read the registry
316 # Hope it's on the path
318 res = WhereIs('makensis.exe')
319 conf.Result(res is not None)
323 def checkLC_MESSAGES(conf):
324 ''' check the definition of LC_MESSAGES '''
325 check_LC_MESSAGES = '''
332 conf.Message('Checking for LC_MESSAGES in locale.h... ')
333 ret = conf.TryLink(check_LC_MESSAGES, '.c')
338 def checkIconvConst(conf):
339 ''' check the declaration of iconv '''
340 check_iconv_const = '''
342 // this declaration will fail when there already exists a non const char**
343 // version which returns size_t
344 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
349 conf.Message('Checking if the declaration of iconv needs const... ')
350 ret = conf.TryLink(check_iconv_const, '.c')
355 def checkSizeOfWChar(conf):
356 ''' check the size of wchar '''
357 check_sizeof_wchar = '''
358 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
364 conf.Message('Checking the size of wchar_t... ')
365 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
367 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
371 conf.Result(str(ret))
375 def createConfigFile(conf, config_file,
376 config_pre = '', config_post = '',
377 headers = [], functions = [], types = [], libs = [],
378 custom_tests = [], extra_items = []):
379 ''' create a configuration file, with options
380 config_file: which file to create
381 config_pre: first part of the config file
382 config_post: last part of the config file
383 headers: header files to check, in the form of a list of
384 ('file', 'HAVE_FILE', 'c'/'c++')
385 functions: functions to check, in the form of a list of
386 ('func', 'HAVE_func', 'include lines'/None)
387 types: types to check, in the form of a list of
388 ('type', 'HAVE_TYPE', 'includelines'/None)
389 libs: libraries to check, in the form of a list of
390 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
391 or any of the libs exists if 'lib' is a list of libs.
392 Optionally, user can provide another key LIB_NAME, that will
393 be set to the detected lib (or None otherwise).
394 custom_tests: extra tests to perform, in the form of a list of
395 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
396 If the last two are ignored, '#define key 1' '/*#undef key */'
398 extra_items: extra configuration lines, in the form of a list of
399 ('config', 'description')
401 The result of each test, as a dictioanry of
402 res['XXX'] = True/False
403 XXX are keys defined in each argument.
405 cont = config_pre + '\n'
407 # add to this string, in appropriate format
408 def configString(lines, desc=''):
410 if lines.strip() != '':
412 text += '/* ' + desc + ' */\n'
413 text += lines + '\n\n'
417 for header in headers:
418 description = "Define to 1 if you have the <%s> header file." % header[0]
419 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
420 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
421 result[header[1]] = 1
422 cont += configString('#define %s 1' % header[1], desc = description)
424 result[header[1]] = 0
425 cont += configString('/* #undef %s */' % header[1], desc = description)
427 for func in functions:
428 description = "Define to 1 if you have the `%s' function." % func[0]
429 if conf.CheckFunc(func[0], header=func[2]):
431 cont += configString('#define %s 1' % func[1], desc = description)
434 cont += configString('/* #undef %s */' % func[1], desc = description)
437 description = "Define to 1 if you have the `%s' type." % t[0]
438 if conf.CheckType(t[0], includes=t[2]):
440 cont += configString('#define %s 1' % t[1], desc = description)
443 cont += configString('/* #undef %s */' % t[1], desc = description)
446 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
447 if type(lib[0]) is type(''):
451 # check if any of the lib exists
453 # if user want the name of the lib detected
455 result[lib[2]] = None
457 if conf.CheckLib(ll):
461 cont += configString('#define %s 1' % lib[1], desc = description)
464 if not result[lib[1]]:
465 cont += configString('/* #undef %s */' % lib[1], desc = description)
467 for test in custom_tests:
471 cont += configString('#define %s 1' % test[1], desc = test[2])
473 cont += configString(test[3], desc = test[2])
477 cont += configString('/* #undef %s */' % test[1], desc = test[2])
479 cont += configString(test[4], desc = test[2])
480 # extra items (no key is returned)
481 for item in extra_items:
482 cont += configString(item[0], desc = item[1])
484 cont += '\n' + config_post + '\n'
486 writeToFile(config_file, cont)
490 def installCygwinLDScript(path):
491 ''' Install i386pe.x-no-rdata '''
492 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
493 script = open(ld_script, 'w')
494 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
496 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
497 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
499 OUTPUT_FORMAT(pei-i386)
500 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
501 ENTRY(_mainCRTStartup)
504 .text __image_base__ + __section_alignment__ :
511 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
512 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
513 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
514 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
516 /* ??? Why is .gcc_exc here? */
521 /* The Cygwin32 library uses a section to avoid copying certain data
522 on fork. This used to be named ".data". The linker used
523 to include this between __data_start__ and __data_end__, but that
524 breaks building the cygwin32 dll. Instead, we name the section
525 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
526 .data BLOCK(__section_alignment__) :
535 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
536 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
537 *(.rdata_runtime_pseudo_reloc)
538 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
539 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
541 *(.data_cygwin_nocopy)
543 .rdata BLOCK(__section_alignment__) :
546 .pdata BLOCK(__section_alignment__) :
550 .bss BLOCK(__section_alignment__) :
557 .edata BLOCK(__section_alignment__) :
568 .idata BLOCK(__section_alignment__) :
570 /* This cannot currently be handled with grouped sections.
571 See pe.em:sort_sections. */
574 /* These zeroes mark the end of the import list. */
575 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
581 .CRT BLOCK(__section_alignment__) :
583 ___crt_xc_start__ = . ;
584 *(SORT(.CRT$XC*)) /* C initialization */
585 ___crt_xc_end__ = . ;
586 ___crt_xi_start__ = . ;
587 *(SORT(.CRT$XI*)) /* C++ initialization */
588 ___crt_xi_end__ = . ;
589 ___crt_xl_start__ = . ;
590 *(SORT(.CRT$XL*)) /* TLS callbacks */
591 /* ___crt_xl_end__ is defined in the TLS Directory support code */
592 ___crt_xp_start__ = . ;
593 *(SORT(.CRT$XP*)) /* Pre-termination */
594 ___crt_xp_end__ = . ;
595 ___crt_xt_start__ = . ;
596 *(SORT(.CRT$XT*)) /* Termination */
597 ___crt_xt_end__ = . ;
599 .tls BLOCK(__section_alignment__) :
607 .endjunk BLOCK(__section_alignment__) :
609 /* end is deprecated, don't use it */
614 .rsrc BLOCK(__section_alignment__) :
619 .reloc BLOCK(__section_alignment__) :
623 .stab BLOCK(__section_alignment__) (NOLOAD) :
627 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
631 /* DWARF debug sections.
632 Symbols in the DWARF debugging sections are relative to the beginning
633 of the section. Unlike other targets that fake this by putting the
634 section VMA at 0, the PE format will not allow it. */
635 /* DWARF 1.1 and DWARF 2. */
636 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
640 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
645 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
647 *(.debug_info) *(.gnu.linkonce.wi.*)
649 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
653 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
657 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
661 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
665 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
669 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
673 /* SGI/MIPS DWARF 2 extensions. */
674 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
678 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
682 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
686 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
691 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
701 def installCygwinPostinstallScript(path):
702 ''' Install lyx.sh '''
703 postinstall_script = os.path.join(path, 'lyx.sh')
704 script = open(postinstall_script, 'w')
705 script.write('''#!/bin/sh
707 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
708 # if it is not already there.
709 if [ -f /etc/fonts/local.conf ]; then
710 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
711 if [ $? -ne 0 ]; then
712 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
713 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
714 fc-cache /usr/share/lyx/fonts
719 return(postinstall_script)
723 # these will be used under win32
729 # does not matter if it fails on other systems
734 def __init__(self, env, logfile, longarg, info):
735 # save the spawn system
737 self.logfile = logfile
738 # clear the logfile (it may not exist)
740 # this will overwrite existing content.
741 writeToFile(logfile, info, append=False)
743 self.longarg = longarg
744 # get hold of the old spawn? (necessary?)
745 self._spawn = env['SPAWN']
748 def spawn(self, sh, escape, cmd, args, spawnenv):
750 newargs = ' '.join(map(escape, args[1:]))
751 cmdline = cmd + " " + newargs
753 # if log is not empty, write to it
754 if self.logfile != '':
755 # this tend to be slow (?) but ensure correct output
756 # Note that cmdline may be long so I do not escape it
758 # since this is not an essential operation, proceed if things go wrong here.
759 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
761 print "Warning: can not write to log file ", self.logfile
763 # if the command is not too long, use the old
764 if not self.longarg or len(cmdline) < 8000:
765 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
767 sAttrs = win32security.SECURITY_ATTRIBUTES()
768 StartupInfo = win32process.STARTUPINFO()
770 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
771 # check for any special operating system commands
774 win32file.DeleteFile(arg)
777 # otherwise execute the command.
778 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
779 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
780 exit_code = win32process.GetExitCodeProcess(hProcess)
781 win32file.CloseHandle(hProcess);
782 win32file.CloseHandle(hThread);
786 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
787 ''' This function modify env and allow logging of
788 commands to a logfile. If the argument is too long
789 a win32 spawn will be used instead of the system one
792 # create a new spwn object
793 ls = loggedSpawn(env, logfile, longarg, info)
794 # replace the old SPAWN by the new function
795 env['SPAWN'] = ls.spawn