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)
82 def checkPkgConfig(conf, version):
83 ''' Return false if pkg_config does not exist, or is too old '''
84 conf.Message('Checking for pkg-config...')
85 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
90 def checkPackage(conf, pkg):
91 ''' check if pkg is under the control of conf '''
92 conf.Message('Checking for package %s...' % pkg)
93 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
98 def checkMkdirOneArg(conf):
99 check_mkdir_one_arg_source = """
100 #include <sys/stat.h>
106 conf.Message('Checking for the number of args for mkdir... ')
107 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
108 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
109 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
117 def checkCXXGlobalCstd(conf):
118 ''' Check the use of std::tolower or tolower '''
119 check_global_cstd_source = '''
127 conf.Message('Check for the use of global cstd... ')
128 ret = conf.TryLink(check_global_cstd_source, '.c')
133 def checkSelectArgType(conf):
134 ''' Adapted from autoconf '''
135 conf.Message('Checking for arg types for select... ')
136 for arg234 in ['fd_set *', 'int *', 'void *']:
137 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
138 for arg5 in ['struct timeval *', 'const struct timeval *']:
139 check_select_source = '''
140 #if HAVE_SYS_SELECT_H
141 # include <sys/select.h>
143 #if HAVE_SYS_SOCKET_H
144 # include <sys/socket.h>
146 extern int select (%s, %s, %s, %s, %s);
151 ''' % (arg1, arg234, arg234, arg234, arg5)
152 ret = conf.TryLink(check_select_source, '.c')
155 return (arg1, arg234, arg5)
156 conf.Result('no (use default)')
157 return ('int', 'int *', 'struct timeval *')
160 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
161 ''' look for boost libraries
163 lib_paths: try these paths for boost libraries
164 inc_paths: try these paths for boost headers
165 versions: supported boost versions
166 isDebug: if true, use debug libraries
168 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
169 libprefix = conf.env['LIBPREFIX']
170 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
176 for path in lib_paths:
177 conf.Log("Looking into %s\n" % path)
179 # get all the libs, then filter for the right library
180 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
181 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
183 conf.Log("Find boost libraries: %s\n" % files)
184 # runtime code includes s,g,y,d,p,n, where we should look for
185 # d,g,y for debug, s,p,n for release
189 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
192 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
193 if len(lib_files) == 0:
194 # use alternative libraries
196 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
197 if len(lib_files) > 0:
198 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
199 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
200 lib_names.append(name.split('.')[0])
201 conf.Log("Qualified libraries: %s\n" % lib_names)
203 conf.Log("No qualified library is found.\n")
205 if len(lib_names) == len(libs):
210 if len(lib_names) == 0:
211 conf.Log("No boost library is found\n")
213 conf.Log("Found boost libraries: %s\n" % lib_names)
215 return (None, None, None)
216 # check version number in boost/version.hpp
217 def isValidBoostDir(dir):
218 version_file = os.path.join(dir, 'boost', 'version.hpp')
219 if not os.path.isfile(version_file):
221 version_file_content = open(version_file).read()
222 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
223 return True in [x in version_file_content for x in version_strings]
224 # check for boost header file
225 for path in inc_paths:
226 conf.Log("Checking for inc path: %s\n" % path)
227 if isValidBoostDir(path):
230 else: # check path/boost_1_xx_x/boost
231 dirs = glob.glob(os.path.join(path, 'boost-*'))
232 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
233 conf.Log("Checing for sub directory: %s\n" % dirs[0])
239 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
240 return (lib_names, lib_path, inc_path)
243 return (None, None, None)
246 def checkCommand(conf, cmd):
247 ''' check the existence of a command
248 return full path to the command, or none
250 conf.Message('Checking for command %s...' % cmd)
252 conf.Result(res is not None)
256 def checkLC_MESSAGES(conf):
257 ''' check the definition of LC_MESSAGES '''
258 check_LC_MESSAGES = '''
265 conf.Message('Check for LC_MESSAGES in locale.h... ')
266 ret = conf.TryLink(check_LC_MESSAGES, '.c')
271 def checkIconvConst(conf):
272 ''' check the declaration of iconv '''
273 check_iconv_const = '''
275 // this declaration will fail when there already exists a non const char**
276 // version which returns size_t
277 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
282 conf.Message('Check if the declaration of iconv needs const... ')
283 ret = conf.TryLink(check_iconv_const, '.c')
288 def checkSizeOfWChar(conf):
289 ''' check the size of wchar '''
290 check_sizeof_wchar = '''
291 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
297 conf.Message('Check the size of wchar_t... ')
298 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
300 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
304 conf.Result(str(ret))
308 def createConfigFile(conf, config_file,
309 config_pre = '', config_post = '',
310 headers = [], functions = [], types = [], libs = [],
311 custom_tests = [], extra_items = []):
312 ''' create a configuration file, with options
313 config_file: which file to create
314 config_pre: first part of the config file
315 config_post: last part of the config file
316 headers: header files to check, in the form of a list of
317 ('file', 'HAVE_FILE', 'c'/'c++')
318 functions: functions to check, in the form of a list of
319 ('func', 'HAVE_func', 'include lines'/None)
320 types: types to check, in the form of a list of
321 ('type', 'HAVE_TYPE', 'includelines'/None)
322 libs: libraries to check, in the form of a list of
323 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
324 or any of the libs exists if 'lib' is a list of libs.
325 Optionally, user can provide another key LIB_NAME, that will
326 be set to the detected lib (or None otherwise).
327 custom_tests: extra tests to perform, in the form of a list of
328 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
329 If the last two are ignored, '#define key 1' '/*#undef key */'
331 extra_items: extra configuration lines, in the form of a list of
332 ('config', 'description')
334 The result of each test, as a dictioanry of
335 res['XXX'] = True/False
336 XXX are keys defined in each argument.
338 cont = config_pre + '\n'
340 # add to this string, in appropriate format
341 def configString(lines, desc=''):
343 if lines.strip() != '':
345 text += '/* ' + desc + ' */\n'
346 text += lines + '\n\n'
350 for header in headers:
351 description = "Define to 1 if you have the <%s> header file." % header[0]
352 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
353 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
354 result[header[1]] = 1
355 cont += configString('#define %s 1' % header[1], desc = description)
357 result[header[1]] = 0
358 cont += configString('/* #undef %s */' % header[1], desc = description)
360 for func in functions:
361 description = "Define to 1 if you have the `%s' function." % func[0]
362 if conf.CheckFunc(func[0], header=func[2]):
364 cont += configString('#define %s 1' % func[1], desc = description)
367 cont += configString('/* #undef %s */' % func[1], desc = description)
370 description = "Define to 1 if you have the `%s' type." % t[0]
371 if conf.CheckType(t[0], includes=t[2]):
373 cont += configString('#define %s 1' % t[1], desc = description)
376 cont += configString('/* #undef %s */' % t[1], desc = description)
379 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
380 if type(lib[0]) is type(''):
384 # check if any of the lib exists
386 # if user want the name of the lib detected
388 result[lib[2]] = None
390 if conf.CheckLib(ll):
394 cont += configString('#define %s 1' % lib[1], desc = description)
397 if not result[lib[1]]:
398 cont += configString('/* #undef %s */' % lib[1], desc = description)
400 for test in custom_tests:
404 cont += configString('#define %s 1' % test[1], desc = test[2])
406 cont += configString(test[3], desc = test[2])
410 cont += configString('/* #undef %s */' % test[1], desc = test[2])
412 cont += configString(test[4], desc = test[2])
413 # extra items (no key is returned)
414 for item in extra_items:
415 cont += configString(item[0], desc = item[1])
417 cont += '\n' + config_post + '\n'
419 writeToFile(config_file, cont)
423 def installCygwinLDScript(path):
424 ''' Install i386pe.x-no-rdata '''
425 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
426 script = open(ld_script, 'w')
427 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
429 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
430 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
432 OUTPUT_FORMAT(pei-i386)
433 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
434 ENTRY(_mainCRTStartup)
437 .text __image_base__ + __section_alignment__ :
444 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
445 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
446 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
447 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
449 /* ??? Why is .gcc_exc here? */
454 /* The Cygwin32 library uses a section to avoid copying certain data
455 on fork. This used to be named ".data". The linker used
456 to include this between __data_start__ and __data_end__, but that
457 breaks building the cygwin32 dll. Instead, we name the section
458 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
459 .data BLOCK(__section_alignment__) :
468 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
469 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
470 *(.rdata_runtime_pseudo_reloc)
471 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
472 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
474 *(.data_cygwin_nocopy)
476 .rdata BLOCK(__section_alignment__) :
479 .pdata BLOCK(__section_alignment__) :
483 .bss BLOCK(__section_alignment__) :
490 .edata BLOCK(__section_alignment__) :
501 .idata BLOCK(__section_alignment__) :
503 /* This cannot currently be handled with grouped sections.
504 See pe.em:sort_sections. */
507 /* These zeroes mark the end of the import list. */
508 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
514 .CRT BLOCK(__section_alignment__) :
516 ___crt_xc_start__ = . ;
517 *(SORT(.CRT$XC*)) /* C initialization */
518 ___crt_xc_end__ = . ;
519 ___crt_xi_start__ = . ;
520 *(SORT(.CRT$XI*)) /* C++ initialization */
521 ___crt_xi_end__ = . ;
522 ___crt_xl_start__ = . ;
523 *(SORT(.CRT$XL*)) /* TLS callbacks */
524 /* ___crt_xl_end__ is defined in the TLS Directory support code */
525 ___crt_xp_start__ = . ;
526 *(SORT(.CRT$XP*)) /* Pre-termination */
527 ___crt_xp_end__ = . ;
528 ___crt_xt_start__ = . ;
529 *(SORT(.CRT$XT*)) /* Termination */
530 ___crt_xt_end__ = . ;
532 .tls BLOCK(__section_alignment__) :
540 .endjunk BLOCK(__section_alignment__) :
542 /* end is deprecated, don't use it */
547 .rsrc BLOCK(__section_alignment__) :
552 .reloc BLOCK(__section_alignment__) :
556 .stab BLOCK(__section_alignment__) (NOLOAD) :
560 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
564 /* DWARF debug sections.
565 Symbols in the DWARF debugging sections are relative to the beginning
566 of the section. Unlike other targets that fake this by putting the
567 section VMA at 0, the PE format will not allow it. */
568 /* DWARF 1.1 and DWARF 2. */
569 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
573 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
578 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
580 *(.debug_info) *(.gnu.linkonce.wi.*)
582 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
586 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
590 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
594 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
598 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
602 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
606 /* SGI/MIPS DWARF 2 extensions. */
607 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
611 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
615 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
619 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
624 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
634 def installCygwinPostinstallScript(path):
635 ''' Install lyx.sh '''
636 postinstall_script = os.path.join(path, 'lyx.sh')
637 script = open(postinstall_script, 'w')
638 script.write('''#!/bin/sh
640 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
641 # if it is not already there.
642 if [ -f /etc/fonts/local.conf ]; then
643 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
644 if [ $? -ne 0 ]; then
645 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
646 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
647 fc-cache /usr/share/lyx/fonts
652 return(postinstall_script)
656 # these will be used under win32
662 # does not matter if it fails on other systems
667 def __init__(self, env, logfile, longarg, info):
668 # save the spawn system
670 self.logfile = logfile
671 # clear the logfile (it may not exist)
673 # this will overwrite existing content.
674 writeToFile(logfile, info, append=False)
676 self.longarg = longarg
677 # get hold of the old spawn? (necessary?)
678 self._spawn = env['SPAWN']
681 def spawn(self, sh, escape, cmd, args, spawnenv):
683 newargs = ' '.join(map(escape, args[1:]))
684 cmdline = cmd + " " + newargs
686 # if log is not empty, write to it
687 if self.logfile != '':
688 # this tend to be slow (?) but ensure correct output
689 # Note that cmdline may be long so I do not escape it
691 # since this is not an essential operation, proceed if things go wrong here.
692 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
694 print "Warning: can not write to log file ", self.logfile
696 # if the command is not too long, use the old
697 if not self.longarg or len(cmdline) < 8000:
698 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
700 sAttrs = win32security.SECURITY_ATTRIBUTES()
701 StartupInfo = win32process.STARTUPINFO()
703 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
704 # check for any special operating system commands
707 win32file.DeleteFile(arg)
710 # otherwise execute the command.
711 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
712 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
713 exit_code = win32process.GetExitCodeProcess(hProcess)
714 win32file.CloseHandle(hProcess);
715 win32file.CloseHandle(hThread);
719 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
720 ''' This function modify env and allow logging of
721 commands to a logfile. If the argument is too long
722 a win32 spawn will be used instead of the system one
725 # create a new spwn object
726 ls = loggedSpawn(env, logfile, longarg, info)
727 # replace the old SPAWN by the new function
728 env['SPAWN'] = ls.spawn