1 # vi:filetype=python:expandtab:tabstop=2:shiftwidth=2
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
18 config_h = os.path.join('src', 'config.h')
21 def writeToFile(filename, lines, append = False):
22 " utility function: write or append lines to filename "
24 file = open(filename, 'a')
26 file = open(filename, 'w')
31 def printEnvironment(env, keys=[]):
32 ''' used to check profile settings '''
33 dict = env.Dictionary()
39 # try to expand, but this is not always possible
40 print key, '=', env.subst('$'+key)
42 print '<<UNEXPANDED>>:', key, '=', dict[key]
45 def env_subst(target, source, env):
46 ''' subst variables in source by those in env, and output to target
47 source and target are scons File() objects
49 %key% (not key itself) is an indication of substitution
51 assert len(target) == 1
52 assert len(source) == 1
53 target_file = file(str(target[0]), "w")
54 source_file = file(str(source[0]), "r")
56 contents = source_file.read()
57 for k, v in env.items():
59 val = env.subst('$'+k)
60 # temporary fix for the \Resource backslash problem
61 val = val.replace('\\', '/')
62 # multi-line replacement
63 val = val.replace('\n',r'\\n\\\n')
64 contents = re.sub('@'+k+'@', val, contents)
65 contents = re.sub('%'+k+'%', val, contents)
68 target_file.write(contents + "\n")
70 #st = os.stat(str(source[0]))
71 #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
76 def globSource(dir, pattern, build_dir=None, exclude=[], include=[]):
77 ''' glob files, in dir and use build_dir as returned path name '''
78 files = filter(lambda x: x not in exclude, glob.glob1(dir, pattern)) + include
82 return ['%s/%s' % (build_dir, x) for x in files]
88 def checkPkgConfig(conf, version):
89 ''' Return false if pkg_config does not exist, or is too old '''
90 conf.Message('Checking for pkg-config...')
91 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
96 def checkPackage(conf, pkg):
97 ''' check if pkg is under the control of conf '''
98 conf.Message('Checking for package %s...' % pkg)
99 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
105 ''' Write the first part of config.h '''
106 global config_content
107 config_content = '''/* src/config.h. Generated by scon. */
112 * This file is part of LyX, the document processor.
113 * Licence details can be found in the file COPYING.
115 * This is the compilation configuration file for LyX.
116 * It was generated by scon.
117 * You might want to change some of the defaults if something goes wrong
118 * during the compilation.
126 def addToConfig(lines, desc=''):
127 ''' utility function: shortcut for appending lines to outfile
128 add newline at the end of lines.
130 global config_content
131 if lines.strip() != '':
133 config_content += desc + '\n'
134 config_content += lines + '\n\n'
137 def endConfigH(top_src_dir):
138 ''' Write the last part of config.h '''
139 global config_content
140 writeToFile(os.path.join(top_src_dir, config_h), config_content +
141 '''/************************************************************
142 ** You should not need to change anything beyond this point */
144 #ifndef HAVE_STRERROR
145 #if defined(__cplusplus)
148 char * strerror(int n);
152 #ifndef HAVE_DECL_MKSTEMP
153 #if defined(__cplusplus)
160 #if defined(HAVE_OSTREAM) && defined(HAVE_LOCALE) && defined(HAVE_SSTREAM)
161 # define USE_BOOST_FORMAT 1
163 # define USE_BOOST_FORMAT 0
166 #define BOOST_USER_CONFIG <config.h>
168 #if !defined(ENABLE_ASSERTIONS)
169 # define BOOST_DISABLE_ASSERTS 1
171 #define BOOST_ENABLE_ASSERT_HANDLER 1
173 #define BOOST_DISABLE_THREADS 1
174 #define BOOST_NO_WREGEX 1
175 #define BOOST_NO_WSTRING 1
178 # define BOOST_POSIX 1
181 #if defined(HAVE_NEWAPIS_H)
182 # define WANT_GETFILEATTRIBUTESEX_WRAPPER 1
190 def checkMkdirOneArg(conf):
191 check_mkdir_one_arg_source = """
192 #include <sys/stat.h>
198 conf.Message('Checking for the number of args for mkdir... ')
199 ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
200 conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
201 conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
210 def checkCXXGlobalCstd(conf):
211 ''' Check the use of std::tolower or tolower '''
212 check_global_cstd_source = '''
220 conf.Message('Check for the use of global cstd... ')
221 ret = conf.TryLink(check_global_cstd_source, '.c')
229 def checkSelectArgType(conf):
230 ''' Adapted from autoconf '''
231 conf.Message('Checking for arg types for select... ')
232 for arg234 in ['fd_set *', 'int *', 'void *']:
233 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
234 for arg5 in ['struct timeval *', 'const struct timeval *']:
235 check_select_source = '''
236 #if HAVE_SYS_SELECT_H
237 # include <sys/select.h>
239 #if HAVE_SYS_SOCKET_H
240 # include <sys/socket.h>
242 extern int select (%s, %s, %s, %s, %s);
247 ''' % (arg1, arg234, arg234, arg234, arg5)
248 ret = conf.TryLink(check_select_source, '.c')
251 return (arg1, arg234, arg5)
252 conf.Result('no (use default)')
253 return ('int', 'int *', 'struct timeval *')
256 def checkBoostLibraries(conf, lib, pathes):
257 ''' look for boost libraries '''
258 conf.Message('Checking for boost library %s... ' % lib)
260 # direct form: e.g. libboost_iostreams.a
261 if os.path.isfile(os.path.join(path, 'lib%s.a' % lib)):
264 # check things like libboost_iostreams-gcc.a
265 files = glob.glob(os.path.join(path, 'lib%s-*.a' % lib))
266 # if there are more than one, choose the first one
267 # FIXME: choose the best one.
269 # get xxx-gcc from /usr/local/lib/libboost_xxx-gcc.a
271 return (path, files[0].split(os.sep)[-1][3:-2])
276 def checkCommand(conf, cmd):
277 ''' check the existence of a command
278 return full path to the command, or none
280 conf.Message('Checking for command %s...' % cmd)
282 conf.Result(res is not None)
286 def checkLC_MESSAGES(conf):
287 ''' check the definition of LC_MESSAGES '''
288 check_LC_MESSAGES = '''
295 conf.Message('Check for LC_MESSAGES in locale.h... ')
296 ret = conf.TryLink(check_LC_MESSAGES, '.c')
301 # FIXME: not quite sure about this part.
302 def checkIconvConst(conf):
303 ''' check the declaration of iconv '''
304 check_iconv_const = '''
311 #if defined(__STDC__) || defined(__cplusplus)
312 size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
316 extern size_t iconv(iconv_t cd, const char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
322 conf.Message('Check if the declaration of iconv needs const... ')
323 ret = conf.TryLink(check_iconv_const, '.c')
328 def installCygwinLDScript(path):
329 ''' Install i386pe.x-no-rdata '''
330 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
331 script = open(ld_script, 'w')
332 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
334 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
335 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
337 OUTPUT_FORMAT(pei-i386)
338 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
339 ENTRY(_mainCRTStartup)
342 .text __image_base__ + __section_alignment__ :
349 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
350 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
351 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
352 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
354 /* ??? Why is .gcc_exc here? */
359 /* The Cygwin32 library uses a section to avoid copying certain data
360 on fork. This used to be named ".data". The linker used
361 to include this between __data_start__ and __data_end__, but that
362 breaks building the cygwin32 dll. Instead, we name the section
363 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
364 .data BLOCK(__section_alignment__) :
373 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
374 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
375 *(.rdata_runtime_pseudo_reloc)
376 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
377 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
379 *(.data_cygwin_nocopy)
381 .rdata BLOCK(__section_alignment__) :
384 .pdata BLOCK(__section_alignment__) :
388 .bss BLOCK(__section_alignment__) :
395 .edata BLOCK(__section_alignment__) :
406 .idata BLOCK(__section_alignment__) :
408 /* This cannot currently be handled with grouped sections.
409 See pe.em:sort_sections. */
412 /* These zeroes mark the end of the import list. */
413 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
419 .CRT BLOCK(__section_alignment__) :
421 ___crt_xc_start__ = . ;
422 *(SORT(.CRT$XC*)) /* C initialization */
423 ___crt_xc_end__ = . ;
424 ___crt_xi_start__ = . ;
425 *(SORT(.CRT$XI*)) /* C++ initialization */
426 ___crt_xi_end__ = . ;
427 ___crt_xl_start__ = . ;
428 *(SORT(.CRT$XL*)) /* TLS callbacks */
429 /* ___crt_xl_end__ is defined in the TLS Directory support code */
430 ___crt_xp_start__ = . ;
431 *(SORT(.CRT$XP*)) /* Pre-termination */
432 ___crt_xp_end__ = . ;
433 ___crt_xt_start__ = . ;
434 *(SORT(.CRT$XT*)) /* Termination */
435 ___crt_xt_end__ = . ;
437 .tls BLOCK(__section_alignment__) :
445 .endjunk BLOCK(__section_alignment__) :
447 /* end is deprecated, don't use it */
452 .rsrc BLOCK(__section_alignment__) :
457 .reloc BLOCK(__section_alignment__) :
461 .stab BLOCK(__section_alignment__) (NOLOAD) :
465 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
469 /* DWARF debug sections.
470 Symbols in the DWARF debugging sections are relative to the beginning
471 of the section. Unlike other targets that fake this by putting the
472 section VMA at 0, the PE format will not allow it. */
473 /* DWARF 1.1 and DWARF 2. */
474 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
478 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
483 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
485 *(.debug_info) *(.gnu.linkonce.wi.*)
487 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
491 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
495 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
499 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
503 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
507 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
511 /* SGI/MIPS DWARF 2 extensions. */
512 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
516 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
520 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
524 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
529 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
540 # these will be used under win32
546 # does not matter if it fails on other systems
551 def __init__(self, env, logfile, longarg, info):
552 # save the spawn system
554 self.logfile = logfile
555 # clear the logfile (it may not exist)
557 # this will overwrite existing content.
558 writeToFile(logfile, info, append=False)
560 self.longarg = longarg
561 # get hold of the old spawn? (necessary?)
562 self._spawn = env['SPAWN']
565 def spawn(self, sh, escape, cmd, args, spawnenv):
567 newargs = ' '.join(map(escape, args[1:]))
568 cmdline = cmd + " " + newargs
570 # if log is not empty, write to it
571 if self.logfile != '':
572 # this tend to be slow (?) but ensure correct output
573 # Note that cmdline may be long so I do not escape it
575 # since this is not an essential operation, proceed if things go wrong here.
576 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
578 print "Warning: can not write to log file ", self.logfile
580 # if the command is not too long, use the old
581 if not self.longarg or len(cmdline) < 8000:
582 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
584 sAttrs = win32security.SECURITY_ATTRIBUTES()
585 StartupInfo = win32process.STARTUPINFO()
587 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
588 # check for any special operating system commands
591 win32file.DeleteFile(arg)
594 # otherwise execute the command.
595 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
596 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
597 exit_code = win32process.GetExitCodeProcess(hProcess)
598 win32file.CloseHandle(hProcess);
599 win32file.CloseHandle(hThread);
603 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
604 ''' This function modify env and allow logging of
605 commands to a logfile. If the argument is too long
606 a win32 spawn will be used instead of the system one
609 # create a new spwn object
610 ls = loggedSpawn(env, logfile, longarg, info)
611 # replace the old SPAWN by the new function
612 env['SPAWN'] = ls.spawn
615 ## def DistSources(env, node):
616 ## env.DistFiles(_get_sources(env, node))
618 ## def DistFiles(env, files):
619 ## assert isinstance(files, (list, tuple))
620 ## DISTFILES = [env.File(fname) for fname in files]
621 ## env.AppendUnique(DISTFILES=DISTFILES)
624 ## def make_distdir(target=None, source=None, env=None):
625 ## distdir = env.subst('$DISTDIR')
626 ## Execute(Delete(distdir))
627 ## Execute(Mkdir(distdir))
628 ## for fnode in env["DISTFILES"]:
629 ## dirname, fname = os.path.split(str(fnode))
631 ## distdirname = os.path.join(distdir, dirname)
632 ## if not os.path.exists(distdirname):
633 ## Execute(Mkdir(distdirname))
634 ## Execute(Copy(os.path.join(distdir, dirname, fname), str(fnode)))
636 ## def make_dist(target=None, source=None, env=None):
637 ## return Popen([env['TAR'], "-zcf",
638 ## env.subst("${PACKAGE}-${VERSION}.tar.gz"),
639 ## env.subst('$DISTDIR')]).wait()
641 ## def make_distcheck(target=None, source=None, env=None):
642 ## distdir = env.subst('$DISTDIR')
643 ## distcheckinstdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-instdir-'))
644 ## distcheckdestdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-destdir-'))
645 ## instdirs = [os.path.join(distcheckinstdir, d) for d in
646 ## 'lib', 'share', 'bin', 'include']
647 ## for dir_ in instdirs:
648 ## Execute(Mkdir(dir_))
650 ## cmd = env.subst("cd $DISTDIR && scons DESTDIR=%s prefix=%s"
651 ## " && scons check && scons install") %\
652 ## (os.path.join(distcheckdestdir, ''), distcheckinstdir)
653 ## status = Popen(cmd, shell=True).wait()
656 ## ## Check that inst dirs are empty (to catch cases of $DESTDIR not being honored
657 ## for dir_ in instdirs:
658 ## if os.listdir(dir_):
659 ## raise SCons.Errors.BuildError(target, "%s not empy" % dir_)
660 ## ## Check that something inside $DESTDIR was installed
661 ## dir_ = os.path.join(distcheckdestdir, distcheckinstdir)
662 ## if not os.path.exists(dir_):
663 ## raise SCons.Errors.BuildError(target, "%s does not exist" % dir_)
664 ## Execute(Delete(distcheckinstdir))
665 ## Execute(Delete(distcheckdestdir))
666 ## Execute(Delete(distdir))
668 ## def InstallWithDestDir(self, dir_, source):
669 ## dir_ = '${DESTDIR}' + str(dir_)
670 ## return SConsEnvironment.Install(self, dir_, source)
673 ## def InstallAsWithDestDir(self, target, source):
674 ## target = '${DESTDIR}' + str(target)
675 ## return SConsEnvironment.InstallAs(self, target, source)
677 ## def generate(env):
678 ## env.EnsureSConsVersion(0, 96, 91)
680 ## opts = Options(['options.cache'], ARGUMENTS)
681 ## opts.Add(PathOption('prefix', 'Installation prefix', '/usr/local'))
682 ## opts.Add(PathOption('exec_prefix', 'Installation prefix blah blah',
684 ## opts.Add(PathOption('libdir',
685 ## 'Installation prefix for architecture dependent files', '$prefix/lib'))
686 ## opts.Add(PathOption('includedir',
687 ## 'Installation prefix for C header files', '$prefix/include'))
688 ## opts.Add(PathOption('datadir',
689 ## 'Installation prefix for architecture independent files', '$prefix/share'))
690 ## opts.Add(PathOption('bindir', 'Installation prefix for programs', '$prefix/bin'))
691 ## opts.Add(PathOption('DESTDIR', 'blah blah', None))
693 ## opts.Save('options.cache', env)
694 ## SConsEnvironment.Help(env, opts.GenerateHelpText(env))
696 ## env.Append(CPPFLAGS=r' -DVERSION=\"$VERSION\"')
697 ## env.Append(CCFLAGS=ARGUMENTS.get('CCFLAGS', '-g -O2'))
699 ## env['GNOME_TESTS'] = dict(CheckPython=CheckPython,
700 ## CheckPythonHeaders=CheckPythonHeaders,
701 ## PkgCheckModules=PkgCheckModules)
703 ## SConsEnvironment.DistSources = DistSources
704 ## SConsEnvironment.DistFiles = DistFiles
705 ## env['DISTDIR'] = "${PACKAGE}-${VERSION}"
707 ## #env.Command(env.Dir("$DISTDIR"), None, make_distdir)
709 ## distdir_alias = env.Alias("distdir", None, make_distdir)
710 ## dist_alias = env.Alias("dist", None, make_dist)
711 ## env.Depends(dist_alias, distdir_alias)
712 ## distcheck_alias = env.Alias("distcheck", None, make_distcheck)
713 ## env.Depends(distcheck_alias, distdir_alias)
714 ## env.AlwaysBuild(env.Alias('check'))
716 ## #env['TARFLAGS'] ='-c -z'
717 ## #env['TARSUFFIX'] = '.tar.gz'
718 ## #tar = env.Tar('${PACKAGE}-${VERSION}.tar.gz', "${DISTDIR}")
719 ## #env.Depends(tar, distdir_alias)
720 ## #print env['DEFAULT_TARGETS']
722 ## #env.Depends(distdir_alias, "${DISTFILES}")
723 ## #env.Alias('dist', tar)
724 ## env.AlwaysBuild('dist')
725 ## env.AlwaysBuild('distdir')
726 ## env.AlwaysBuild('distcheck')
727 ## env.DistFiles(['SConstruct', 'scons/gnome.py'])
729 ## env['BUILDERS']['EnvSubstFile'] = SCons.Builder.Builder(action=env_subst)
731 ## SConsEnvironment.PythonByteCompile = env.Action(byte_compile_python)
733 ## env.Install = new.instancemethod(InstallWithDestDir, env, env.__class__)
734 ## env.InstallAs = new.instancemethod(InstallAsWithDestDir, env, env.__class__)