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 if '-bundle.exe' in str(target[0]):
99 ret += '/DSETUPTYPE_BUNDLE=1 '
101 ret += quoteIfSpaced(str(s))
105 def createResFromIcon(env, icon_file, rc_file):
106 ''' create a rc file with icon, and return res file (windows only) '''
108 rc_name = env.File(rc_file).abspath
109 dir = os.path.split(rc_name)[0]
110 if not os.path.isdir(dir):
112 rc = open(rc_name, 'w')
113 print >> rc, 'IDI_ICON1 ICON DISCARDABLE "%s"' % \
114 os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
115 'packaging', 'icons', icon_file).replace('\\', '\\\\')
117 return env.RES(rc_name)
126 def checkPkgConfig(conf, version):
127 ''' Return false if pkg_config does not exist, or is too old '''
128 conf.Message('Checking for pkg-config...')
129 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
134 def checkPackage(conf, pkg):
135 ''' check if pkg is under the control of conf '''
136 conf.Message('Checking for package %s...' % pkg)
137 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
142 def checkMkdirOneArg(conf):
143 check_mkdir_one_arg_source = """
144 #include <sys/stat.h>
150 conf.Message('Checking for the number of args for mkdir... ')
151 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
152 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
153 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
161 def checkCXXGlobalCstd(conf):
162 ''' Checking the use of std::tolower or tolower '''
163 check_global_cstd_source = '''
171 conf.Message('Checking for the use of global cstd... ')
172 ret = conf.TryLink(check_global_cstd_source, '.c')
177 def checkSelectArgType(conf):
178 ''' Adapted from autoconf '''
179 conf.Message('Checking for arg types for select... ')
180 for arg234 in ['fd_set *', 'int *', 'void *']:
181 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
182 for arg5 in ['struct timeval *', 'const struct timeval *']:
183 check_select_source = '''
184 #if HAVE_SYS_SELECT_H
185 # include <sys/select.h>
187 #if HAVE_SYS_SOCKET_H
188 # include <sys/socket.h>
190 extern int select (%s, %s, %s, %s, %s);
195 ''' % (arg1, arg234, arg234, arg234, arg5)
196 ret = conf.TryLink(check_select_source, '.c')
199 return (arg1, arg234, arg5)
200 conf.Result('no (use default)')
201 return ('int', 'int *', 'struct timeval *')
204 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
205 ''' look for boost libraries
207 lib_paths: try these paths for boost libraries
208 inc_paths: try these paths for boost headers
209 versions: supported boost versions
210 isDebug: if true, use debug libraries
212 conf.Message('Checking for boost library %s... ' % ', '.join(libs))
213 libprefix = conf.env['LIBPREFIX']
214 libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
220 for path in lib_paths:
221 conf.Log("Looking into %s\n" % path)
223 # get all the libs, then filter for the right library
224 files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
225 # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
227 conf.Log("Find boost libraries: %s\n" % files)
228 # runtime code includes s,g,y,d,p,n, where we should look for
229 # d,g,y for debug, s,p,n for release
233 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
236 lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
237 if len(lib_files) == 0:
238 # use alternative libraries
240 lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
241 if len(lib_files) > 0:
242 # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
243 name = lib_files[0].split(os.sep)[-1][len(libprefix):]
244 lib_names.append(name.split('.')[0])
245 conf.Log("Qualified libraries: %s\n" % lib_names)
247 conf.Log("No qualified library is found.\n")
249 if len(lib_names) == len(libs):
254 if len(lib_names) == 0:
255 conf.Log("No boost library is found\n")
257 conf.Log("Found boost libraries: %s\n" % lib_names)
259 return (None, None, None)
260 # check version number in boost/version.hpp
261 def isValidBoostDir(dir):
262 version_file = os.path.join(dir, 'boost', 'version.hpp')
263 if not os.path.isfile(version_file):
265 version_file_content = open(version_file).read()
266 version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
267 return True in [x in version_file_content for x in version_strings]
268 # check for boost header file
269 for path in inc_paths:
270 conf.Log("Checking for inc path: %s\n" % path)
271 if isValidBoostDir(path):
274 else: # check path/boost_1_xx_x/boost
275 dirs = glob.glob(os.path.join(path, 'boost-*'))
276 if len(dirs) > 0 and isValidBoostDir(dirs[0]):
277 conf.Log("Checing for sub directory: %s\n" % dirs[0])
283 conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
284 return (lib_names, lib_path, inc_path)
287 return (None, None, None)
290 def checkCommand(conf, cmd):
291 ''' check the existence of a command
292 return full path to the command, or none
294 conf.Message('Checking for command %s...' % cmd)
296 conf.Result(res is not None)
301 ''' check the existence of nsis compiler, return the fullpath '''
302 conf.Message('Checking for nsis compiler...')
305 # If we can read the registry, get the NSIS command from it
307 k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
309 val, tok = RegQueryValueEx(k,None)
310 ret = val + os.path.sep + 'makensis.exe'
311 if os.path.isfile(ret):
312 res = '"' + ret + '"'
316 pass # Couldn't find the key, just act like we can't read the registry
317 # Hope it's on the path
319 res = WhereIs('makensis.exe')
320 conf.Result(res is not None)
324 def checkLC_MESSAGES(conf):
325 ''' check the definition of LC_MESSAGES '''
326 check_LC_MESSAGES = '''
333 conf.Message('Checking for LC_MESSAGES in locale.h... ')
334 ret = conf.TryLink(check_LC_MESSAGES, '.c')
339 def checkIconvConst(conf):
340 ''' check the declaration of iconv '''
341 check_iconv_const = '''
343 // this declaration will fail when there already exists a non const char**
344 // version which returns size_t
345 double iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
350 conf.Message('Checking if the declaration of iconv needs const... ')
351 ret = conf.TryLink(check_iconv_const, '.c')
356 def checkSizeOfWChar(conf):
357 ''' check the size of wchar '''
358 check_sizeof_wchar = '''
359 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
365 conf.Message('Checking the size of wchar_t... ')
366 if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
368 elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
372 conf.Result(str(ret))
376 def createConfigFile(conf, config_file,
377 config_pre = '', config_post = '',
378 headers = [], functions = [], types = [], libs = [],
379 custom_tests = [], extra_items = []):
380 ''' create a configuration file, with options
381 config_file: which file to create
382 config_pre: first part of the config file
383 config_post: last part of the config file
384 headers: header files to check, in the form of a list of
385 ('file', 'HAVE_FILE', 'c'/'c++')
386 functions: functions to check, in the form of a list of
387 ('func', 'HAVE_func', 'include lines'/None)
388 types: types to check, in the form of a list of
389 ('type', 'HAVE_TYPE', 'includelines'/None)
390 libs: libraries to check, in the form of a list of
391 ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
392 or any of the libs exists if 'lib' is a list of libs.
393 Optionally, user can provide another key LIB_NAME, that will
394 be set to the detected lib (or None otherwise).
395 custom_tests: extra tests to perform, in the form of a list of
396 (test (True/False), 'key', 'desc', 'true config line', 'false config line')
397 If the last two are ignored, '#define key 1' '/*#undef key */'
399 extra_items: extra configuration lines, in the form of a list of
400 ('config', 'description')
402 The result of each test, as a dictioanry of
403 res['XXX'] = True/False
404 XXX are keys defined in each argument.
406 cont = config_pre + '\n'
408 # add to this string, in appropriate format
409 def configString(lines, desc=''):
411 if lines.strip() != '':
413 text += '/* ' + desc + ' */\n'
414 text += lines + '\n\n'
418 for header in headers:
419 description = "Define to 1 if you have the <%s> header file." % header[0]
420 if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
421 (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
422 result[header[1]] = 1
423 cont += configString('#define %s 1' % header[1], desc = description)
425 result[header[1]] = 0
426 cont += configString('/* #undef %s */' % header[1], desc = description)
428 for func in functions:
429 description = "Define to 1 if you have the `%s' function." % func[0]
430 if conf.CheckFunc(func[0], header=func[2]):
432 cont += configString('#define %s 1' % func[1], desc = description)
435 cont += configString('/* #undef %s */' % func[1], desc = description)
438 description = "Define to 1 if you have the `%s' type." % t[0]
439 if conf.CheckType(t[0], includes=t[2]):
441 cont += configString('#define %s 1' % t[1], desc = description)
444 cont += configString('/* #undef %s */' % t[1], desc = description)
447 description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
448 if type(lib[0]) is type(''):
452 # check if any of the lib exists
454 # if user want the name of the lib detected
456 result[lib[2]] = None
458 if conf.CheckLib(ll):
462 cont += configString('#define %s 1' % lib[1], desc = description)
465 if not result[lib[1]]:
466 cont += configString('/* #undef %s */' % lib[1], desc = description)
468 for test in custom_tests:
472 cont += configString('#define %s 1' % test[1], desc = test[2])
474 cont += configString(test[3], desc = test[2])
478 cont += configString('/* #undef %s */' % test[1], desc = test[2])
480 cont += configString(test[4], desc = test[2])
481 # extra items (no key is returned)
482 for item in extra_items:
483 cont += configString(item[0], desc = item[1])
485 cont += '\n' + config_post + '\n'
487 writeToFile(config_file, cont)
491 def installCygwinLDScript(path):
492 ''' Install i386pe.x-no-rdata '''
493 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
494 script = open(ld_script, 'w')
495 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
497 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
498 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
500 OUTPUT_FORMAT(pei-i386)
501 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
502 ENTRY(_mainCRTStartup)
505 .text __image_base__ + __section_alignment__ :
512 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
513 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
514 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
515 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
517 /* ??? Why is .gcc_exc here? */
522 /* The Cygwin32 library uses a section to avoid copying certain data
523 on fork. This used to be named ".data". The linker used
524 to include this between __data_start__ and __data_end__, but that
525 breaks building the cygwin32 dll. Instead, we name the section
526 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
527 .data BLOCK(__section_alignment__) :
536 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
537 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
538 *(.rdata_runtime_pseudo_reloc)
539 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
540 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
542 *(.data_cygwin_nocopy)
544 .rdata BLOCK(__section_alignment__) :
547 .pdata BLOCK(__section_alignment__) :
551 .bss BLOCK(__section_alignment__) :
558 .edata BLOCK(__section_alignment__) :
569 .idata BLOCK(__section_alignment__) :
571 /* This cannot currently be handled with grouped sections.
572 See pe.em:sort_sections. */
575 /* These zeroes mark the end of the import list. */
576 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
582 .CRT BLOCK(__section_alignment__) :
584 ___crt_xc_start__ = . ;
585 *(SORT(.CRT$XC*)) /* C initialization */
586 ___crt_xc_end__ = . ;
587 ___crt_xi_start__ = . ;
588 *(SORT(.CRT$XI*)) /* C++ initialization */
589 ___crt_xi_end__ = . ;
590 ___crt_xl_start__ = . ;
591 *(SORT(.CRT$XL*)) /* TLS callbacks */
592 /* ___crt_xl_end__ is defined in the TLS Directory support code */
593 ___crt_xp_start__ = . ;
594 *(SORT(.CRT$XP*)) /* Pre-termination */
595 ___crt_xp_end__ = . ;
596 ___crt_xt_start__ = . ;
597 *(SORT(.CRT$XT*)) /* Termination */
598 ___crt_xt_end__ = . ;
600 .tls BLOCK(__section_alignment__) :
608 .endjunk BLOCK(__section_alignment__) :
610 /* end is deprecated, don't use it */
615 .rsrc BLOCK(__section_alignment__) :
620 .reloc BLOCK(__section_alignment__) :
624 .stab BLOCK(__section_alignment__) (NOLOAD) :
628 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
632 /* DWARF debug sections.
633 Symbols in the DWARF debugging sections are relative to the beginning
634 of the section. Unlike other targets that fake this by putting the
635 section VMA at 0, the PE format will not allow it. */
636 /* DWARF 1.1 and DWARF 2. */
637 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
641 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
646 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
648 *(.debug_info) *(.gnu.linkonce.wi.*)
650 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
654 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
658 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
662 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
666 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
670 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
674 /* SGI/MIPS DWARF 2 extensions. */
675 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
679 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
683 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
687 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
692 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
702 def installCygwinPostinstallScript(path):
703 ''' Install lyx.sh '''
704 postinstall_script = os.path.join(path, 'lyx.sh')
705 script = open(postinstall_script, 'w')
706 script.write(r'''#!/bin/sh
708 # Add /usr/share/lyx/fonts to /etc/fonts/local.conf
709 # if it is not already there.
710 if [ -f /etc/fonts/local.conf ]; then
711 grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
712 if [ $? -ne 0 ]; then
713 sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
714 mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
715 fc-cache /usr/share/lyx/fonts
720 return(postinstall_script)
724 # these will be used under win32
730 # does not matter if it fails on other systems
735 def __init__(self, env, logfile, longarg, info):
736 # save the spawn system
738 self.logfile = logfile
739 # clear the logfile (it may not exist)
741 # this will overwrite existing content.
742 writeToFile(logfile, info, append=False)
744 self.longarg = longarg
745 # get hold of the old spawn? (necessary?)
746 self._spawn = env['SPAWN']
749 def spawn(self, sh, escape, cmd, args, spawnenv):
751 newargs = ' '.join(map(escape, args[1:]))
752 cmdline = cmd + " " + newargs
754 # if log is not empty, write to it
755 if self.logfile != '':
756 # this tend to be slow (?) but ensure correct output
757 # Note that cmdline may be long so I do not escape it
759 # since this is not an essential operation, proceed if things go wrong here.
760 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
762 print "Warning: can not write to log file ", self.logfile
764 # if the command is not too long, use the old
765 if not self.longarg or len(cmdline) < 8000:
766 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
768 sAttrs = win32security.SECURITY_ATTRIBUTES()
769 StartupInfo = win32process.STARTUPINFO()
771 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
772 # check for any special operating system commands
775 win32file.DeleteFile(arg)
778 # otherwise execute the command.
779 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
780 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
781 exit_code = win32process.GetExitCodeProcess(hProcess)
782 win32file.CloseHandle(hProcess);
783 win32file.CloseHandle(hThread);
787 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
788 ''' This function modify env and allow logging of
789 commands to a logfile. If the argument is too long
790 a win32 spawn will be used instead of the system one
793 # create a new spwn object
794 ls = loggedSpawn(env, logfile, longarg, info)
795 # replace the old SPAWN by the new function
796 env['SPAWN'] = ls.spawn