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 rc = open(rc_name, 'w')
84 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
85 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
86 'packaging', 'icons', icon_file)
88 return env.RES(rc_name)
97 def checkPkgConfig(conf, version):
98 ''' Return false if pkg_config does not exist, or is too old '''
99 conf.Message('Checking for pkg-config...')
100 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
105 def checkPackage(conf, pkg):
106 ''' check if pkg is under the control of conf '''
107 conf.Message('Checking for package %s...' % pkg)
108 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
113 def checkMkdirOneArg(conf):
114 check_mkdir_one_arg_source = """
115 #include <sys/stat.h>
121 conf.Message('Checking for the number of args for mkdir... ')
122 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
123 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
124 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
132 def checkCXXGlobalCstd(conf):
133 ''' Check the use of std::tolower or tolower '''
134 check_global_cstd_source = '''
142 conf.Message('Check for the use of global cstd... ')
143 ret = conf.TryLink(check_global_cstd_source, '.c')
148 def checkSelectArgType(conf):
149 ''' Adapted from autoconf '''
150 conf.Message('Checking for arg types for select... ')
151 for arg234 in ['fd_set *', 'int *', 'void *']:
152 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
153 for arg5 in ['struct timeval *', 'const struct timeval *']:
154 check_select_source = '''
155 #if HAVE_SYS_SELECT_H
156 # include <sys/select.h>
158 #if HAVE_SYS_SOCKET_H
159 # include <sys/socket.h>
161 extern int select (%s, %s, %s, %s, %s);
166 ''' % (arg1, arg234, arg234, arg234, arg5)
167 ret = conf.TryLink(check_select_source, '.c')
170 return (arg1, arg234, arg5)
171 conf.Result('no (use default)')
172 return ('int', 'int *', 'struct timeval *')
175 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
176 ''' look for boost libraries
178 lib_paths: try these paths for boost libraries
179 inc_paths: try these paths for boost headers
180 versions: supported boost versions
181 isDebug: if true, use debug libraries
183 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
184 libprefix = conf.env['LIBPREFIX']
185 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
191 for path in lib_paths:
192 conf.Log("Looking into %s\n" % path)
194 # get all the libs, then filter for the right library
195 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
196 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
198 conf.Log("Find boost libraries: %s\n" % files)
199 # runtime code includes s,g,y,d,p,n, where we should look for
200 # d,g,y for debug, s,p,n for release
204 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
207 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
208 if len(lib_files) == 0:
209 # use alternative libraries
211 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
212 if len(lib_files) > 0:
213 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
214 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
215 lib_names.append(name.split('.')[0])
216 conf.Log("Qualified libraries: %s\n" % lib_names)
218 conf.Log("No qualified library is found.\n")
220 if len(lib_names) == len(libs):
225 if len(lib_names) == 0:
226 conf.Log("No boost library is found\n")
228 conf.Log("Found boost libraries: %s\n" % lib_names)
230 return (None, None, None)
231 # check version number in boost/version.hpp
232 def isValidBoostDir(dir):
233 version_file = os.path.join(dir, 'boost', 'version.hpp')
234 if not os.path.isfile(version_file):
236 version_file_content = open(version_file).read()
237 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
238 return True in [x in version_file_content for x in version_strings]
239 # check for boost header file
240 for path in inc_paths:
241 conf.Log("Checking for inc path: %s\n" % path)
242 if isValidBoostDir(path):
245 else: # check path/boost_1_xx_x/boost
246 dirs = glob.glob(os.path.join(path, 'boost-*'))
247 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
248 conf.Log("Checing for sub directory: %s\n" % dirs[0])
254 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
255 return (lib_names, lib_path, inc_path)
258 return (None, None, None)
261 def checkCommand(conf, cmd):
262 ''' check the existence of a command
263 return full path to the command, or none
265 conf.Message('Checking for command %s...' % cmd)
267 conf.Result(res is not None)
271 def checkLC_MESSAGES(conf):
272 ''' check the definition of LC_MESSAGES '''
273 check_LC_MESSAGES = '''
280 conf.Message('Check for LC_MESSAGES in locale.h... ')
281 ret = conf.TryLink(check_LC_MESSAGES, '.c')
286 def checkIconvConst(conf):
287 ''' check the declaration of iconv '''
288 check_iconv_const = '''
290 // this declaration will fail when there already exists a non const char**
291 // version which returns size_t
292 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
297 conf.Message('Check if the declaration of iconv needs const... ')
298 ret = conf.TryLink(check_iconv_const, '.c')
303 def checkSizeOfWChar(conf):
304 ''' check the size of wchar '''
305 check_sizeof_wchar = '''
306 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
312 conf.Message('Check the size of wchar_t... ')
313 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
315 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
319 conf.Result(str(ret))
323 def createConfigFile(conf, config_file,
324 config_pre = '', config_post = '',
325 headers = [], functions = [], types = [], libs = [],
326 custom_tests = [], extra_items = []):
327 ''' create a configuration file, with options
328 config_file: which file to create
329 config_pre: first part of the config file
330 config_post: last part of the config file
331 headers: header files to check, in the form of a list of
332 ('file', 'HAVE_FILE', 'c'/'c++')
333 functions: functions to check, in the form of a list of
334 ('func', 'HAVE_func', 'include lines'/None)
335 types: types to check, in the form of a list of
336 ('type', 'HAVE_TYPE', 'includelines'/None)
337 libs: libraries to check, in the form of a list of
338 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
339 or any of the libs exists if 'lib' is a list of libs.
340 Optionally, user can provide another key LIB_NAME, that will
341 be set to the detected lib (or None otherwise).
342 custom_tests: extra tests to perform, in the form of a list of
343 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
344 If the last two are ignored, '#define key 1' '/*#undef key */'
346 extra_items: extra configuration lines, in the form of a list of
347 ('config', 'description')
349 The result of each test, as a dictioanry of
350 res['XXX'] = True/False
351 XXX are keys defined in each argument.
353 cont = config_pre + '\n'
355 # add to this string, in appropriate format
356 def configString(lines, desc=''):
358 if lines.strip() != '':
360 text += '/* ' + desc + ' */\n'
361 text += lines + '\n\n'
365 for header in headers:
366 description = "Define to 1 if you have the <%s> header file." % header[0]
367 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
368 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
369 result[header[1]] = 1
370 cont += configString('#define %s 1' % header[1], desc = description)
372 result[header[1]] = 0
373 cont += configString('/* #undef %s */' % header[1], desc = description)
375 for func in functions:
376 description = "Define to 1 if you have the `%s' function." % func[0]
377 if conf.CheckFunc(func[0], header=func[2]):
379 cont += configString('#define %s 1' % func[1], desc = description)
382 cont += configString('/* #undef %s */' % func[1], desc = description)
385 description = "Define to 1 if you have the `%s' type." % t[0]
386 if conf.CheckType(t[0], includes=t[2]):
388 cont += configString('#define %s 1' % t[1], desc = description)
391 cont += configString('/* #undef %s */' % t[1], desc = description)
394 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
395 if type(lib[0]) is type(''):
399 # check if any of the lib exists
401 # if user want the name of the lib detected
403 result[lib[2]] = None
405 if conf.CheckLib(ll):
409 cont += configString('#define %s 1' % lib[1], desc = description)
412 if not result[lib[1]]:
413 cont += configString('/* #undef %s */' % lib[1], desc = description)
415 for test in custom_tests:
419 cont += configString('#define %s 1' % test[1], desc = test[2])
421 cont += configString(test[3], desc = test[2])
425 cont += configString('/* #undef %s */' % test[1], desc = test[2])
427 cont += configString(test[4], desc = test[2])
428 # extra items (no key is returned)
429 for item in extra_items:
430 cont += configString(item[0], desc = item[1])
432 cont += '\n' + config_post + '\n'
434 writeToFile(config_file, cont)
438 def installCygwinLDScript(path):
439 ''' Install i386pe.x-no-rdata '''
440 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
441 script = open(ld_script, 'w')
442 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
444 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
445 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
447 OUTPUT_FORMAT(pei-i386)
448 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
449 ENTRY(_mainCRTStartup)
452 .text __image_base__ + __section_alignment__ :
459 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
460 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
461 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
462 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
464 /* ??? Why is .gcc_exc here? */
469 /* The Cygwin32 library uses a section to avoid copying certain data
470 on fork. This used to be named ".data". The linker used
471 to include this between __data_start__ and __data_end__, but that
472 breaks building the cygwin32 dll. Instead, we name the section
473 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
474 .data BLOCK(__section_alignment__) :
483 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
484 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
485 *(.rdata_runtime_pseudo_reloc)
486 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
487 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
489 *(.data_cygwin_nocopy)
491 .rdata BLOCK(__section_alignment__) :
494 .pdata BLOCK(__section_alignment__) :
498 .bss BLOCK(__section_alignment__) :
505 .edata BLOCK(__section_alignment__) :
516 .idata BLOCK(__section_alignment__) :
518 /* This cannot currently be handled with grouped sections.
519 See pe.em:sort_sections. */
522 /* These zeroes mark the end of the import list. */
523 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
529 .CRT BLOCK(__section_alignment__) :
531 ___crt_xc_start__ = . ;
532 *(SORT(.CRT$XC*)) /* C initialization */
533 ___crt_xc_end__ = . ;
534 ___crt_xi_start__ = . ;
535 *(SORT(.CRT$XI*)) /* C++ initialization */
536 ___crt_xi_end__ = . ;
537 ___crt_xl_start__ = . ;
538 *(SORT(.CRT$XL*)) /* TLS callbacks */
539 /* ___crt_xl_end__ is defined in the TLS Directory support code */
540 ___crt_xp_start__ = . ;
541 *(SORT(.CRT$XP*)) /* Pre-termination */
542 ___crt_xp_end__ = . ;
543 ___crt_xt_start__ = . ;
544 *(SORT(.CRT$XT*)) /* Termination */
545 ___crt_xt_end__ = . ;
547 .tls BLOCK(__section_alignment__) :
555 .endjunk BLOCK(__section_alignment__) :
557 /* end is deprecated, don't use it */
562 .rsrc BLOCK(__section_alignment__) :
567 .reloc BLOCK(__section_alignment__) :
571 .stab BLOCK(__section_alignment__) (NOLOAD) :
575 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
579 /* DWARF debug sections.
580 Symbols in the DWARF debugging sections are relative to the beginning
581 of the section. Unlike other targets that fake this by putting the
582 section VMA at 0, the PE format will not allow it. */
583 /* DWARF 1.1 and DWARF 2. */
584 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
588 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
593 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
595 *(.debug_info) *(.gnu.linkonce.wi.*)
597 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
601 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
605 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
609 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
613 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
617 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
621 /* SGI/MIPS DWARF 2 extensions. */
622 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
626 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
630 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
634 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
639 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
649 def installCygwinPostinstallScript(path):
650 ''' Install lyx.sh '''
651 postinstall_script = os.path.join(path, 'lyx.sh')
652 script = open(postinstall_script, 'w')
653 script.write('''#!/bin/sh
655 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
656 # if it is not already there.
657 if [ -f /etc/fonts/local.conf ]; then
658 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
659 if [ $? -ne 0 ]; then
660 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
661 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
662 fc-cache /usr/share/lyx/fonts
667 return(postinstall_script)
671 # these will be used under win32
677 # does not matter if it fails on other systems
682 def __init__(self, env, logfile, longarg, info):
683 # save the spawn system
685 self.logfile = logfile
686 # clear the logfile (it may not exist)
688 # this will overwrite existing content.
689 writeToFile(logfile, info, append=False)
691 self.longarg = longarg
692 # get hold of the old spawn? (necessary?)
693 self._spawn = env['SPAWN']
696 def spawn(self, sh, escape, cmd, args, spawnenv):
698 newargs = ' '.join(map(escape, args[1:]))
699 cmdline = cmd + " " + newargs
701 # if log is not empty, write to it
702 if self.logfile != '':
703 # this tend to be slow (?) but ensure correct output
704 # Note that cmdline may be long so I do not escape it
706 # since this is not an essential operation, proceed if things go wrong here.
707 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
709 print "Warning: can not write to log file ", self.logfile
711 # if the command is not too long, use the old
712 if not self.longarg or len(cmdline) < 8000:
713 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
715 sAttrs = win32security.SECURITY_ATTRIBUTES()
716 StartupInfo = win32process.STARTUPINFO()
718 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
719 # check for any special operating system commands
722 win32file.DeleteFile(arg)
725 # otherwise execute the command.
726 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
727 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
728 exit_code = win32process.GetExitCodeProcess(hProcess)
729 win32file.CloseHandle(hProcess);
730 win32file.CloseHandle(hThread);
734 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
735 ''' This function modify env and allow logging of
736 commands to a logfile. If the argument is too long
737 a win32 spawn will be used instead of the system one
740 # create a new spwn object
741 ls = loggedSpawn(env, logfile, longarg, info)
742 # replace the old SPAWN by the new function
743 env['SPAWN'] = ls.spawn