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'] + " /V1 "
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(env['NSISDEFINES'][d])
98 ret += quoteIfSpaced(str(s))
102 def createResFromIcon(env, icon_file, rc_file):
103 ''' create a rc file with icon, and return res file (windows only) '''
105 rc_name = env.File(rc_file).abspath
106 dir = os.path.split(rc_name)[0]
107 if not os.path.isdir(dir):
109 rc = open(rc_name, 'w')
110 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
111 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
112 'packaging', 'icons', icon_file).replace('\\', '\\\\')
114 return env.RES(rc_name)
123 def checkPkgConfig(conf, version):
124 ''' Return false if pkg_config does not exist, or is too old '''
125 conf.Message('Checking for pkg-config...')
126 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
131 def checkPackage(conf, pkg):
132 ''' check if pkg is under the control of conf '''
133 conf.Message('Checking for package %s...' % pkg)
134 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
139 def checkMkdirOneArg(conf):
140 check_mkdir_one_arg_source = """
141 #include <sys/stat.h>
147 conf.Message('Checking for the number of args for mkdir... ')
148 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
149 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
150 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
158 def checkCXXGlobalCstd(conf):
159 ''' Checking the use of std::tolower or tolower '''
160 check_global_cstd_source = '''
168 conf.Message('Checking for the use of global cstd... ')
169 ret = conf.TryLink(check_global_cstd_source, '.c')
174 def checkSelectArgType(conf):
175 ''' Adapted from autoconf '''
176 conf.Message('Checking for arg types for select... ')
177 for arg234 in ['fd_set *', 'int *', 'void *']:
178 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
179 for arg5 in ['struct timeval *', 'const struct timeval *']:
180 check_select_source = '''
181 #if HAVE_SYS_SELECT_H
182 # include <sys/select.h>
184 #if HAVE_SYS_SOCKET_H
185 # include <sys/socket.h>
187 extern int select (%s, %s, %s, %s, %s);
192 ''' % (arg1, arg234, arg234, arg234, arg5)
193 ret = conf.TryLink(check_select_source, '.c')
196 return (arg1, arg234, arg5)
197 conf.Result('no (use default)')
198 return ('int', 'int *', 'struct timeval *')
201 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
202 ''' look for boost libraries
204 lib_paths: try these paths for boost libraries
205 inc_paths: try these paths for boost headers
206 versions: supported boost versions
207 isDebug: if true, use debug libraries
209 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
210 libprefix = conf.env['LIBPREFIX']
211 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
217 for path in lib_paths:
218 conf.Log("Looking into %s\n" % path)
220 # get all the libs, then filter for the right library
221 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
222 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
224 conf.Log("Find boost libraries: %s\n" % files)
225 # runtime code includes s,g,y,d,p,n, where we should look for
226 # d,g,y for debug, s,p,n for release
230 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
233 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
234 if len(lib_files) == 0:
235 # use alternative libraries
237 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
238 if len(lib_files) > 0:
239 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
240 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
241 lib_names.append(name.split('.')[0])
242 conf.Log("Qualified libraries: %s\n" % lib_names)
244 conf.Log("No qualified library is found.\n")
246 if len(lib_names) == len(libs):
251 if len(lib_names) == 0:
252 conf.Log("No boost library is found\n")
254 conf.Log("Found boost libraries: %s\n" % lib_names)
256 return (None, None, None)
257 # check version number in boost/version.hpp
258 def isValidBoostDir(dir):
259 version_file = os.path.join(dir, 'boost', 'version.hpp')
260 if not os.path.isfile(version_file):
262 version_file_content = open(version_file).read()
263 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
264 return True in [x in version_file_content for x in version_strings]
265 # check for boost header file
266 for path in inc_paths:
267 conf.Log("Checking for inc path: %s\n" % path)
268 if isValidBoostDir(path):
271 else: # check path/boost_1_xx_x/boost
272 dirs = glob.glob(os.path.join(path, 'boost-*'))
273 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
274 conf.Log("Checing for sub directory: %s\n" % dirs[0])
280 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
281 return (lib_names, lib_path, inc_path)
284 return (None, None, None)
287 def checkCommand(conf, cmd):
288 ''' check the existence of a command
289 return full path to the command, or none
291 conf.Message('Checking for command %s...' % cmd)
293 conf.Result(res is not None)
298 ''' check the existence of nsis compiler, return the fullpath '''
299 conf.Message('Checking for nsis compiler...')
302 # If we can read the registry, get the NSIS command from it
304 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
306 val, tok = RegQueryValueEx(k,None)
307 ret = val + os.path.sep + 'makensis.exe'
308 if os.path.isfile(ret):
309 res = '"' + ret + '"'
313 pass # Couldn't find the key, just act like we can't read the registry
314 # Hope it's on the path
316 res = WhereIs('makensis.exe')
317 conf.Result(res is not None)
321 def checkLC_MESSAGES(conf):
322 ''' check the definition of LC_MESSAGES '''
323 check_LC_MESSAGES = '''
330 conf.Message('Checking for LC_MESSAGES in locale.h... ')
331 ret = conf.TryLink(check_LC_MESSAGES, '.c')
336 def checkIconvConst(conf):
337 ''' check the declaration of iconv '''
338 check_iconv_const = '''
340 // this declaration will fail when there already exists a non const char**
341 // version which returns size_t
342 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
347 conf.Message('Checking if the declaration of iconv needs const... ')
348 ret = conf.TryLink(check_iconv_const, '.c')
353 def checkSizeOfWChar(conf):
354 ''' check the size of wchar '''
355 check_sizeof_wchar = '''
356 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
362 conf.Message('Checking the size of wchar_t... ')
363 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
365 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
369 conf.Result(str(ret))
373 def createConfigFile(conf, config_file,
374 config_pre = '', config_post = '',
375 headers = [], functions = [], types = [], libs = [],
376 custom_tests = [], extra_items = []):
377 ''' create a configuration file, with options
378 config_file: which file to create
379 config_pre: first part of the config file
380 config_post: last part of the config file
381 headers: header files to check, in the form of a list of
382 ('file', 'HAVE_FILE', 'c'/'c++')
383 functions: functions to check, in the form of a list of
384 ('func', 'HAVE_func', 'include lines'/None)
385 types: types to check, in the form of a list of
386 ('type', 'HAVE_TYPE', 'includelines'/None)
387 libs: libraries to check, in the form of a list of
388 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
389 or any of the libs exists if 'lib' is a list of libs.
390 Optionally, user can provide another key LIB_NAME, that will
391 be set to the detected lib (or None otherwise).
392 custom_tests: extra tests to perform, in the form of a list of
393 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
394 If the last two are ignored, '#define key 1' '/*#undef key */'
396 extra_items: extra configuration lines, in the form of a list of
397 ('config', 'description')
399 The result of each test, as a dictioanry of
400 res['XXX'] = True/False
401 XXX are keys defined in each argument.
403 cont = config_pre + '\n'
405 # add to this string, in appropriate format
406 def configString(lines, desc=''):
408 if lines.strip() != '':
410 text += '/* ' + desc + ' */\n'
411 text += lines + '\n\n'
415 for header in headers:
416 description = "Define to 1 if you have the <%s> header file." % header[0]
417 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
418 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
419 result[header[1]] = 1
420 cont += configString('#define %s 1' % header[1], desc = description)
422 result[header[1]] = 0
423 cont += configString('/* #undef %s */' % header[1], desc = description)
425 for func in functions:
426 description = "Define to 1 if you have the `%s' function." % func[0]
427 if conf.CheckFunc(func[0], header=func[2]):
429 cont += configString('#define %s 1' % func[1], desc = description)
432 cont += configString('/* #undef %s */' % func[1], desc = description)
435 description = "Define to 1 if you have the `%s' type." % t[0]
436 if conf.CheckType(t[0], includes=t[2]):
438 cont += configString('#define %s 1' % t[1], desc = description)
441 cont += configString('/* #undef %s */' % t[1], desc = description)
444 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
445 if type(lib[0]) is type(''):
449 # check if any of the lib exists
451 # if user want the name of the lib detected
453 result[lib[2]] = None
455 if conf.CheckLib(ll):
459 cont += configString('#define %s 1' % lib[1], desc = description)
462 if not result[lib[1]]:
463 cont += configString('/* #undef %s */' % lib[1], desc = description)
465 for test in custom_tests:
469 cont += configString('#define %s 1' % test[1], desc = test[2])
471 cont += configString(test[3], desc = test[2])
475 cont += configString('/* #undef %s */' % test[1], desc = test[2])
477 cont += configString(test[4], desc = test[2])
478 # extra items (no key is returned)
479 for item in extra_items:
480 cont += configString(item[0], desc = item[1])
482 cont += '\n' + config_post + '\n'
484 writeToFile(config_file, cont)
488 def installCygwinLDScript(path):
489 ''' Install i386pe.x-no-rdata '''
490 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
491 script = open(ld_script, 'w')
492 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
494 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
495 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
497 OUTPUT_FORMAT(pei-i386)
498 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
499 ENTRY(_mainCRTStartup)
502 .text __image_base__ + __section_alignment__ :
509 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
510 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
511 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
512 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
514 /* ??? Why is .gcc_exc here? */
519 /* The Cygwin32 library uses a section to avoid copying certain data
520 on fork. This used to be named ".data". The linker used
521 to include this between __data_start__ and __data_end__, but that
522 breaks building the cygwin32 dll. Instead, we name the section
523 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
524 .data BLOCK(__section_alignment__) :
533 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
534 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
535 *(.rdata_runtime_pseudo_reloc)
536 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
537 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
539 *(.data_cygwin_nocopy)
541 .rdata BLOCK(__section_alignment__) :
544 .pdata BLOCK(__section_alignment__) :
548 .bss BLOCK(__section_alignment__) :
555 .edata BLOCK(__section_alignment__) :
566 .idata BLOCK(__section_alignment__) :
568 /* This cannot currently be handled with grouped sections.
569 See pe.em:sort_sections. */
572 /* These zeroes mark the end of the import list. */
573 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
579 .CRT BLOCK(__section_alignment__) :
581 ___crt_xc_start__ = . ;
582 *(SORT(.CRT$XC*)) /* C initialization */
583 ___crt_xc_end__ = . ;
584 ___crt_xi_start__ = . ;
585 *(SORT(.CRT$XI*)) /* C++ initialization */
586 ___crt_xi_end__ = . ;
587 ___crt_xl_start__ = . ;
588 *(SORT(.CRT$XL*)) /* TLS callbacks */
589 /* ___crt_xl_end__ is defined in the TLS Directory support code */
590 ___crt_xp_start__ = . ;
591 *(SORT(.CRT$XP*)) /* Pre-termination */
592 ___crt_xp_end__ = . ;
593 ___crt_xt_start__ = . ;
594 *(SORT(.CRT$XT*)) /* Termination */
595 ___crt_xt_end__ = . ;
597 .tls BLOCK(__section_alignment__) :
605 .endjunk BLOCK(__section_alignment__) :
607 /* end is deprecated, don't use it */
612 .rsrc BLOCK(__section_alignment__) :
617 .reloc BLOCK(__section_alignment__) :
621 .stab BLOCK(__section_alignment__) (NOLOAD) :
625 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
629 /* DWARF debug sections.
630 Symbols in the DWARF debugging sections are relative to the beginning
631 of the section. Unlike other targets that fake this by putting the
632 section VMA at 0, the PE format will not allow it. */
633 /* DWARF 1.1 and DWARF 2. */
634 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
638 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
643 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
645 *(.debug_info) *(.gnu.linkonce.wi.*)
647 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
651 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
655 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
659 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
663 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
667 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
671 /* SGI/MIPS DWARF 2 extensions. */
672 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
676 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
680 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
684 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
689 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
699 def installCygwinPostinstallScript(path):
700 ''' Install lyx.sh '''
701 postinstall_script = os.path.join(path, 'lyx.sh')
702 script = open(postinstall_script, 'w')
703 script.write('''#!/bin/sh
705 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
706 # if it is not already there.
707 if [ -f /etc/fonts/local.conf ]; then
708 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
709 if [ $? -ne 0 ]; then
710 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
711 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
712 fc-cache /usr/share/lyx/fonts
717 return(postinstall_script)
721 # these will be used under win32
727 # does not matter if it fails on other systems
732 def __init__(self, env, logfile, longarg, info):
733 # save the spawn system
735 self.logfile = logfile
736 # clear the logfile (it may not exist)
738 # this will overwrite existing content.
739 writeToFile(logfile, info, append=False)
741 self.longarg = longarg
742 # get hold of the old spawn? (necessary?)
743 self._spawn = env['SPAWN']
746 def spawn(self, sh, escape, cmd, args, spawnenv):
748 newargs = ' '.join(map(escape, args[1:]))
749 cmdline = cmd + " " + newargs
751 # if log is not empty, write to it
752 if self.logfile != '':
753 # this tend to be slow (?) but ensure correct output
754 # Note that cmdline may be long so I do not escape it
756 # since this is not an essential operation, proceed if things go wrong here.
757 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
759 print "Warning: can not write to log file ", self.logfile
761 # if the command is not too long, use the old
762 if not self.longarg or len(cmdline) < 8000:
763 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
765 sAttrs = win32security.SECURITY_ATTRIBUTES()
766 StartupInfo = win32process.STARTUPINFO()
768 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
769 # check for any special operating system commands
772 win32file.DeleteFile(arg)
775 # otherwise execute the command.
776 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
777 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
778 exit_code = win32process.GetExitCodeProcess(hProcess)
779 win32file.CloseHandle(hProcess);
780 win32file.CloseHandle(hThread);
784 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
785 ''' This function modify env and allow logging of
786 commands to a logfile. If the argument is too long
787 a win32 spawn will be used instead of the system one
790 # create a new spwn object
791 ls = loggedSpawn(env, logfile, longarg, info)
792 # replace the old SPAWN by the new function
793 env['SPAWN'] = ls.spawn