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 WhereIs
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 createResFromIcon(env, icon_file, rc_file):
80 ''' create a rc file with icon, and return res file (windows only) '''
82 rc_name = env.File(rc_file).abspath
83 dir = os.path.split(rc_name)[0]
84 if not os.path.isdir(dir):
86 rc = open(rc_name, 'w')
87 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
88 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
89 'packaging', 'icons', icon_file).replace('\\', '\\\\')
91 return env.RES(rc_name)
100 def checkPkgConfig(conf, version):
101 ''' Return false if pkg_config does not exist, or is too old '''
102 conf.Message('Checking for pkg-config...')
103 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
108 def checkPackage(conf, pkg):
109 ''' check if pkg is under the control of conf '''
110 conf.Message('Checking for package %s...' % pkg)
111 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
116 def checkMkdirOneArg(conf):
117 check_mkdir_one_arg_source = """
118 #include <sys/stat.h>
124 conf.Message('Checking for the number of args for mkdir... ')
125 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
126 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
127 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
135 def checkCXXGlobalCstd(conf):
136 ''' Check the use of std::tolower or tolower '''
137 check_global_cstd_source = '''
145 conf.Message('Check for the use of global cstd... ')
146 ret = conf.TryLink(check_global_cstd_source, '.c')
151 def checkSelectArgType(conf):
152 ''' Adapted from autoconf '''
153 conf.Message('Checking for arg types for select... ')
154 for arg234 in ['fd_set *', 'int *', 'void *']:
155 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
156 for arg5 in ['struct timeval *', 'const struct timeval *']:
157 check_select_source = '''
158 #if HAVE_SYS_SELECT_H
159 # include <sys/select.h>
161 #if HAVE_SYS_SOCKET_H
162 # include <sys/socket.h>
164 extern int select (%s, %s, %s, %s, %s);
169 ''' % (arg1, arg234, arg234, arg234, arg5)
170 ret = conf.TryLink(check_select_source, '.c')
173 return (arg1, arg234, arg5)
174 conf.Result('no (use default)')
175 return ('int', 'int *', 'struct timeval *')
178 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
179 ''' look for boost libraries
181 lib_paths: try these paths for boost libraries
182 inc_paths: try these paths for boost headers
183 versions: supported boost versions
184 isDebug: if true, use debug libraries
186 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
187 libprefix = conf.env['LIBPREFIX']
188 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
194 for path in lib_paths:
195 conf.Log("Looking into %s\n" % path)
197 # get all the libs, then filter for the right library
198 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
199 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
201 conf.Log("Find boost libraries: %s\n" % files)
202 # runtime code includes s,g,y,d,p,n, where we should look for
203 # d,g,y for debug, s,p,n for release
207 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
210 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
211 if len(lib_files) == 0:
212 # use alternative libraries
214 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
215 if len(lib_files) > 0:
216 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
217 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
218 lib_names.append(name.split('.')[0])
219 conf.Log("Qualified libraries: %s\n" % lib_names)
221 conf.Log("No qualified library is found.\n")
223 if len(lib_names) == len(libs):
228 if len(lib_names) == 0:
229 conf.Log("No boost library is found\n")
231 conf.Log("Found boost libraries: %s\n" % lib_names)
233 return (None, None, None)
234 # check version number in boost/version.hpp
235 def isValidBoostDir(dir):
236 version_file = os.path.join(dir, 'boost', 'version.hpp')
237 if not os.path.isfile(version_file):
239 version_file_content = open(version_file).read()
240 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
241 return True in [x in version_file_content for x in version_strings]
242 # check for boost header file
243 for path in inc_paths:
244 conf.Log("Checking for inc path: %s\n" % path)
245 if isValidBoostDir(path):
248 else: # check path/boost_1_xx_x/boost
249 dirs = glob.glob(os.path.join(path, 'boost-*'))
250 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
251 conf.Log("Checing for sub directory: %s\n" % dirs[0])
257 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
258 return (lib_names, lib_path, inc_path)
261 return (None, None, None)
264 def checkCommand(conf, cmd):
265 ''' check the existence of a command
266 return full path to the command, or none
268 conf.Message('Checking for command %s...' % cmd)
270 conf.Result(res is not None)
274 def checkLC_MESSAGES(conf):
275 ''' check the definition of LC_MESSAGES '''
276 check_LC_MESSAGES = '''
283 conf.Message('Check for LC_MESSAGES in locale.h... ')
284 ret = conf.TryLink(check_LC_MESSAGES, '.c')
289 def checkIconvConst(conf):
290 ''' check the declaration of iconv '''
291 check_iconv_const = '''
293 // this declaration will fail when there already exists a non const char**
294 // version which returns size_t
295 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
300 conf.Message('Check if the declaration of iconv needs const... ')
301 ret = conf.TryLink(check_iconv_const, '.c')
306 def checkSizeOfWChar(conf):
307 ''' check the size of wchar '''
308 check_sizeof_wchar = '''
309 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
315 conf.Message('Check the size of wchar_t... ')
316 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
318 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
322 conf.Result(str(ret))
326 def createConfigFile(conf, config_file,
327 config_pre = '', config_post = '',
328 headers = [], functions = [], types = [], libs = [],
329 custom_tests = [], extra_items = []):
330 ''' create a configuration file, with options
331 config_file: which file to create
332 config_pre: first part of the config file
333 config_post: last part of the config file
334 headers: header files to check, in the form of a list of
335 ('file', 'HAVE_FILE', 'c'/'c++')
336 functions: functions to check, in the form of a list of
337 ('func', 'HAVE_func', 'include lines'/None)
338 types: types to check, in the form of a list of
339 ('type', 'HAVE_TYPE', 'includelines'/None)
340 libs: libraries to check, in the form of a list of
341 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
342 or any of the libs exists if 'lib' is a list of libs.
343 Optionally, user can provide another key LIB_NAME, that will
344 be set to the detected lib (or None otherwise).
345 custom_tests: extra tests to perform, in the form of a list of
346 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
347 If the last two are ignored, '#define key 1' '/*#undef key */'
349 extra_items: extra configuration lines, in the form of a list of
350 ('config', 'description')
352 The result of each test, as a dictioanry of
353 res['XXX'] = True/False
354 XXX are keys defined in each argument.
356 cont = config_pre + '\n'
358 # add to this string, in appropriate format
359 def configString(lines, desc=''):
361 if lines.strip() != '':
363 text += '/* ' + desc + ' */\n'
364 text += lines + '\n\n'
368 for header in headers:
369 description = "Define to 1 if you have the <%s> header file." % header[0]
370 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
371 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
372 result[header[1]] = 1
373 cont += configString('#define %s 1' % header[1], desc = description)
375 result[header[1]] = 0
376 cont += configString('/* #undef %s */' % header[1], desc = description)
378 for func in functions:
379 description = "Define to 1 if you have the `%s' function." % func[0]
380 if conf.CheckFunc(func[0], header=func[2]):
382 cont += configString('#define %s 1' % func[1], desc = description)
385 cont += configString('/* #undef %s */' % func[1], desc = description)
388 description = "Define to 1 if you have the `%s' type." % t[0]
389 if conf.CheckType(t[0], includes=t[2]):
391 cont += configString('#define %s 1' % t[1], desc = description)
394 cont += configString('/* #undef %s */' % t[1], desc = description)
397 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
398 if type(lib[0]) is type(''):
402 # check if any of the lib exists
404 # if user want the name of the lib detected
406 result[lib[2]] = None
408 if conf.CheckLib(ll):
412 cont += configString('#define %s 1' % lib[1], desc = description)
415 if not result[lib[1]]:
416 cont += configString('/* #undef %s */' % lib[1], desc = description)
418 for test in custom_tests:
422 cont += configString('#define %s 1' % test[1], desc = test[2])
424 cont += configString(test[3], desc = test[2])
428 cont += configString('/* #undef %s */' % test[1], desc = test[2])
430 cont += configString(test[4], desc = test[2])
431 # extra items (no key is returned)
432 for item in extra_items:
433 cont += configString(item[0], desc = item[1])
435 cont += '\n' + config_post + '\n'
437 writeToFile(config_file, cont)
441 def installCygwinLDScript(path):
442 ''' Install i386pe.x-no-rdata '''
443 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
444 script = open(ld_script, 'w')
445 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
447 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
448 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
450 OUTPUT_FORMAT(pei-i386)
451 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
452 ENTRY(_mainCRTStartup)
455 .text __image_base__ + __section_alignment__ :
462 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
463 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
464 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
465 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
467 /* ??? Why is .gcc_exc here? */
472 /* The Cygwin32 library uses a section to avoid copying certain data
473 on fork. This used to be named ".data". The linker used
474 to include this between __data_start__ and __data_end__, but that
475 breaks building the cygwin32 dll. Instead, we name the section
476 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
477 .data BLOCK(__section_alignment__) :
486 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
487 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
488 *(.rdata_runtime_pseudo_reloc)
489 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
490 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
492 *(.data_cygwin_nocopy)
494 .rdata BLOCK(__section_alignment__) :
497 .pdata BLOCK(__section_alignment__) :
501 .bss BLOCK(__section_alignment__) :
508 .edata BLOCK(__section_alignment__) :
519 .idata BLOCK(__section_alignment__) :
521 /* This cannot currently be handled with grouped sections.
522 See pe.em:sort_sections. */
525 /* These zeroes mark the end of the import list. */
526 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
532 .CRT BLOCK(__section_alignment__) :
534 ___crt_xc_start__ = . ;
535 *(SORT(.CRT$XC*)) /* C initialization */
536 ___crt_xc_end__ = . ;
537 ___crt_xi_start__ = . ;
538 *(SORT(.CRT$XI*)) /* C++ initialization */
539 ___crt_xi_end__ = . ;
540 ___crt_xl_start__ = . ;
541 *(SORT(.CRT$XL*)) /* TLS callbacks */
542 /* ___crt_xl_end__ is defined in the TLS Directory support code */
543 ___crt_xp_start__ = . ;
544 *(SORT(.CRT$XP*)) /* Pre-termination */
545 ___crt_xp_end__ = . ;
546 ___crt_xt_start__ = . ;
547 *(SORT(.CRT$XT*)) /* Termination */
548 ___crt_xt_end__ = . ;
550 .tls BLOCK(__section_alignment__) :
558 .endjunk BLOCK(__section_alignment__) :
560 /* end is deprecated, don't use it */
565 .rsrc BLOCK(__section_alignment__) :
570 .reloc BLOCK(__section_alignment__) :
574 .stab BLOCK(__section_alignment__) (NOLOAD) :
578 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
582 /* DWARF debug sections.
583 Symbols in the DWARF debugging sections are relative to the beginning
584 of the section. Unlike other targets that fake this by putting the
585 section VMA at 0, the PE format will not allow it. */
586 /* DWARF 1.1 and DWARF 2. */
587 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
591 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
596 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
598 *(.debug_info) *(.gnu.linkonce.wi.*)
600 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
604 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
608 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
612 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
616 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
620 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
624 /* SGI/MIPS DWARF 2 extensions. */
625 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
629 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
633 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
637 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
642 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
652 def installCygwinPostinstallScript(path):
653 ''' Install lyx.sh '''
654 postinstall_script = os.path.join(path, 'lyx.sh')
655 script = open(postinstall_script, 'w')
656 script.write('''#!/bin/sh
658 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
659 # if it is not already there.
660 if [ -f /etc/fonts/local.conf ]; then
661 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
662 if [ $? -ne 0 ]; then
663 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
664 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
665 fc-cache /usr/share/lyx/fonts
670 return(postinstall_script)
674 # these will be used under win32
680 # does not matter if it fails on other systems
685 def __init__(self, env, logfile, longarg, info):
686 # save the spawn system
688 self.logfile = logfile
689 # clear the logfile (it may not exist)
691 # this will overwrite existing content.
692 writeToFile(logfile, info, append=False)
694 self.longarg = longarg
695 # get hold of the old spawn? (necessary?)
696 self._spawn = env['SPAWN']
699 def spawn(self, sh, escape, cmd, args, spawnenv):
701 newargs = ' '.join(map(escape, args[1:]))
702 cmdline = cmd + " " + newargs
704 # if log is not empty, write to it
705 if self.logfile != '':
706 # this tend to be slow (?) but ensure correct output
707 # Note that cmdline may be long so I do not escape it
709 # since this is not an essential operation, proceed if things go wrong here.
710 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
712 print "Warning: can not write to log file ", self.logfile
714 # if the command is not too long, use the old
715 if not self.longarg or len(cmdline) < 8000:
716 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
718 sAttrs = win32security.SECURITY_ATTRIBUTES()
719 StartupInfo = win32process.STARTUPINFO()
721 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
722 # check for any special operating system commands
725 win32file.DeleteFile(arg)
728 # otherwise execute the command.
729 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
730 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
731 exit_code = win32process.GetExitCodeProcess(hProcess)
732 win32file.CloseHandle(hProcess);
733 win32file.CloseHandle(hThread);
737 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
738 ''' This function modify env and allow logging of
739 commands to a logfile. If the argument is too long
740 a win32 spawn will be used instead of the system one
743 # create a new spwn object
744 ls = loggedSpawn(env, logfile, longarg, info)
745 # replace the old SPAWN by the new function
746 env['SPAWN'] = ls.spawn