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
17 config_h = os.path.join('src', 'config.h')
19 def writeToFile(filename, lines, append = False):
20 " utility function: write or append lines to filename "
22 file = open(filename, 'a')
24 file = open(filename, 'w')
29 def addToConfig(lines, top_src_dir):
30 ''' utility function: shortcut for appending lines to outfile
31 add newline at the end of lines.
33 if lines.strip() != '':
34 writeToFile(os.path.join(top_src_dir, config_h), lines + '\n\n', append = True)
37 def printEnvironment(env, keys=[]):
38 ''' used to check profile settings '''
39 dict = env.Dictionary()
45 # try to expand, but this is not always possible
46 print key, '=', env.subst('$'+key)
48 print '<<UNEXPANDED>>:', key, '=', dict[key]
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 in env.get('SUBST_KEYS', []):
64 if not env.has_key(k):
65 print "Failed to subst key ", k, " from file", str(source[0])
67 contents = re.sub('@'+k+'@', env.subst('$'+k).replace('\n',r'\\n\\\n'), 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)
74 def env_filecopy(target, source, env):
75 shutil.copy(str(source[0]), str(target[0]))
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 startConfigH(top_src_dir):
99 ''' Write the first part of config.h '''
100 writeToFile(os.path.join(top_src_dir, config_h),
101 '''/* src/config.h. Generated by scon. */
106 * This file is part of LyX, the document processor.
107 * Licence details can be found in the file COPYING.
109 * This is the compilation configuration file for LyX.
110 * It was generated by scon.
111 * You might want to change some of the defaults if something goes wrong
112 * during the compilation.
120 def endConfigH(top_src_dir):
121 ''' Write the last part of config.h '''
122 writeToFile(os.path.join(top_src_dir, config_h), '''
123 /************************************************************
124 ** You should not need to change anything beyond this point */
126 #ifndef HAVE_STRERROR
127 #if defined(__cplusplus)
130 char * strerror(int n);
134 #ifndef HAVE_DECL_MKSTEMP
135 #if defined(__cplusplus)
143 # include "support/os2_defines.h"
146 #if defined(HAVE_OSTREAM) && defined(HAVE_LOCALE) && defined(HAVE_SSTREAM)
147 # define USE_BOOST_FORMAT 1
149 # define USE_BOOST_FORMAT 0
152 #define BOOST_USER_CONFIG <config.h>
154 #if !defined(ENABLE_ASSERTIONS)
155 # define BOOST_DISABLE_ASSERTS 1
157 #define BOOST_ENABLE_ASSERT_HANDLER 1
159 #define BOOST_DISABLE_THREADS 1
160 #define BOOST_NO_WREGEX 1
161 #define BOOST_NO_WSTRING 1
164 # define BOOST_POSIX 1
167 #if defined(HAVE_NEWAPIS_H)
168 # define WANT_GETFILEATTRIBUTESEX_WRAPPER 1
176 def checkPutenv(conf):
177 check_putenv_source = """
185 conf.Message('Checking for putenv... ')
186 ret = conf.TryLink(check_putenv_source, '.c')
191 #HAVE_DECL_ISTREAMBUF_ITERATOR
192 def checkIstreambufIterator(conf):
193 check_istreambuf_iterator_source = """
198 std::istreambuf_iterator<std::istream> iter;
202 conf.Message('Checking for iostreambuf::iterator... ')
203 ret = conf.TryLink(check_istreambuf_iterator_source, '.cpp')
209 def checkMkdirOneArg(conf):
210 check_mkdir_one_arg_source = """
211 #include <sys/stat.h>
217 conf.Message('Checking for the number of args for mkdir... ')
218 ret = conf.TryLink(check_mkdir_one_arg_source, '.c')
227 def checkStdCount(conf):
228 check_std_count_source = """
231 int countChar(char * b, char * e, char const c)
233 return count(b, e, c);
239 int i = countChar(a, a + 5, 'l');
242 conf.Message('Checking for std::count... ')
243 ret = conf.TryLink(check_std_count_source, '.cpp')
251 def checkSelectArgType(conf):
252 ''' Adapted from autoconf '''
253 conf.Message('Checking for arg types for select... ')
254 for arg234 in ['fd_set *', 'int *', 'void *']:
255 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
256 for arg5 in ['struct timeval *', 'const struct timeval *']:
257 check_select_source = '''
258 #if HAVE_SYS_SELECT_H
259 # include <sys/select.h>
261 #if HAVE_SYS_SOCKET_H
262 # include <sys/socket.h>
264 extern int select (%s, %s, %s, %s, %s);
269 ''' % (arg1, arg234, arg234, arg234, arg5)
270 ret = conf.TryLink(check_select_source, '.c')
273 return (arg1, arg234, arg5)
274 conf.Result('no (use default)')
275 return ('int', 'int *', 'struct timeval *')
278 def checkBoostLibraries(conf, lib, pathes):
279 ''' look for boost libraries '''
280 conf.Message('Checking for boost library %s... ' % lib)
282 # direct form: e.g. libboost_iostreams.a
283 if os.path.isfile(os.path.join(path, 'lib%s.a' % lib)):
286 # check things like libboost_iostreams-gcc.a
287 files = glob.glob(os.path.join(path, 'lib%s-*.a' % lib))
288 # if there are more than one, choose the first one
289 # FIXME: choose the best one.
291 # get xxx-gcc from /usr/local/lib/libboost_xxx-gcc.a
293 return (path, files[0].split(os.sep)[-1][3:-2])
299 def processLang(env, folder):
300 """ Process translations (.po files) in a po/ dir
301 This is copied from KDE knetstats-1.5/admin/kde.py
306 dir=SCons.Node.FS.default_fs.Dir(folder).srcnode()
308 tmptransfiles = glob.glob(str(fld)+'/*.po')
311 if env.has_key('_BUILDDIR_'):
312 bdir=env['_BUILDDIR_']
313 for dir in env.make_list(tmptransfiles):
314 transfiles.append( env.join(bdir, dir) )
316 transfiles=tmptransfiles
318 env['MSGFMT'] = 'msgfmt'
319 env['BUILDERS']['Transfiles']=SCons.Builder.Builder(action='$MSGFMT $SOURCE -o $TARGET',suffix='.gmo',src_suffix='.po')
321 # FIXME: KDE has this ARGS thing...
322 #if env['ARGS'] and env['ARGS'].has_key('languages'):
323 # languages=env.make_list(env['ARGS']['languages'])
324 mydir=SCons.Node.FS.default_fs.Dir('.')
326 fname=f.replace(mydir.abspath, '')
327 file=SCons.Node.FS.default_fs.File(fname)
328 country = SCons.Util.splitext(file.name)[0]
329 if not languages or country in languages:
330 result = env.Transfiles(file)
334 def installCygwinLDScript(path):
335 ''' Install i386pe.x-no-rdata '''
336 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
337 script = open(ld_script, 'w')
338 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
340 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
341 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
343 OUTPUT_FORMAT(pei-i386)
344 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
345 ENTRY(_mainCRTStartup)
348 .text __image_base__ + __section_alignment__ :
355 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
356 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
357 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
358 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
360 /* ??? Why is .gcc_exc here? */
365 /* The Cygwin32 library uses a section to avoid copying certain data
366 on fork. This used to be named ".data". The linker used
367 to include this between __data_start__ and __data_end__, but that
368 breaks building the cygwin32 dll. Instead, we name the section
369 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
370 .data BLOCK(__section_alignment__) :
379 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
380 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
381 *(.rdata_runtime_pseudo_reloc)
382 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
383 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
385 *(.data_cygwin_nocopy)
387 .rdata BLOCK(__section_alignment__) :
390 .pdata BLOCK(__section_alignment__) :
394 .bss BLOCK(__section_alignment__) :
401 .edata BLOCK(__section_alignment__) :
412 .idata BLOCK(__section_alignment__) :
414 /* This cannot currently be handled with grouped sections.
415 See pe.em:sort_sections. */
418 /* These zeroes mark the end of the import list. */
419 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
425 .CRT BLOCK(__section_alignment__) :
427 ___crt_xc_start__ = . ;
428 *(SORT(.CRT$XC*)) /* C initialization */
429 ___crt_xc_end__ = . ;
430 ___crt_xi_start__ = . ;
431 *(SORT(.CRT$XI*)) /* C++ initialization */
432 ___crt_xi_end__ = . ;
433 ___crt_xl_start__ = . ;
434 *(SORT(.CRT$XL*)) /* TLS callbacks */
435 /* ___crt_xl_end__ is defined in the TLS Directory support code */
436 ___crt_xp_start__ = . ;
437 *(SORT(.CRT$XP*)) /* Pre-termination */
438 ___crt_xp_end__ = . ;
439 ___crt_xt_start__ = . ;
440 *(SORT(.CRT$XT*)) /* Termination */
441 ___crt_xt_end__ = . ;
443 .tls BLOCK(__section_alignment__) :
451 .endjunk BLOCK(__section_alignment__) :
453 /* end is deprecated, don't use it */
458 .rsrc BLOCK(__section_alignment__) :
463 .reloc BLOCK(__section_alignment__) :
467 .stab BLOCK(__section_alignment__) (NOLOAD) :
471 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
475 /* DWARF debug sections.
476 Symbols in the DWARF debugging sections are relative to the beginning
477 of the section. Unlike other targets that fake this by putting the
478 section VMA at 0, the PE format will not allow it. */
479 /* DWARF 1.1 and DWARF 2. */
480 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
484 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
489 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
491 *(.debug_info) *(.gnu.linkonce.wi.*)
493 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
497 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
501 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
505 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
509 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
513 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
517 /* SGI/MIPS DWARF 2 extensions. */
518 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
522 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
526 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
530 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
535 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
546 # these will be used under win32
552 # does not matter if it fails on other systems
557 def __init__(self, env, logfile, longarg, info):
558 # save the spawn system
560 self.logfile = logfile
561 # clear the logfile (it may not exist)
563 # this will overwrite existing content.
564 writeToFile(logfile, info, append=False)
566 self.longarg = longarg
567 # get hold of the old spawn? (necessary?)
568 self._spawn = env['SPAWN']
571 def spawn(self, sh, escape, cmd, args, spawnenv):
573 newargs = ' '.join(map(escape, args[1:]))
574 cmdline = cmd + " " + newargs
576 # if log is not empty, write to it
577 if self.logfile != '':
578 # this tend to be slow (?) but ensure correct output
579 # Note that cmdline may be long so I do not escape it
581 # since this is not an essential operation, proceed if things go wrong here.
582 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
584 print "Warning: can not write to log file ", self.logfile
586 # if the command is not too long, use the old
587 if not self.longarg or len(cmdline) < 8000:
588 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
590 sAttrs = win32security.SECURITY_ATTRIBUTES()
591 StartupInfo = win32process.STARTUPINFO()
593 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
594 # check for any special operating system commands
597 win32file.DeleteFile(arg)
600 # otherwise execute the command.
601 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
602 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
603 exit_code = win32process.GetExitCodeProcess(hProcess)
604 win32file.CloseHandle(hProcess);
605 win32file.CloseHandle(hThread);
609 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
610 ''' This function modify env and allow logging of
611 commands to a logfile. If the argument is too long
612 a win32 spawn will be used instead of the system one
615 # create a new spwn object
616 ls = loggedSpawn(env, logfile, longarg, info)
617 # replace the old SPAWN by the new function
618 env['SPAWN'] = ls.spawn
621 # Install program with permission
622 # http://www.scons.org/cgi-sys/cgiwrap/scons/moin.cgi/InstallTargets
625 from SCons.Script.SConscript import SConsEnvironment
627 SConsEnvironment.Chmod = SCons.Action.ActionFactory(os.chmod,
628 lambda dest, mode: 'Chmod("%s", 0%o)' % (dest, mode))
630 def installPerm(target, source, env, perm):
631 ''' install program with permission, will copy
632 files recursively if needed '''
633 # FIXME: mixed use of scons and python interfaces?
634 target_dir = str(target[0])
635 if os.path.isfile(target_dir):
636 print "Target should be a directory: ", target_dir
638 if not os.path.isdir(target_dir):
639 os.makedirs(target_dir)
643 print "Installing", fname, "to", target_dir
644 if os.path.isfile(fname):
645 objs.append(os.path.join(target_dir, fname))
646 shutil.copy(fname, target_dir)
647 elif os.path.isdir(fname):
648 # FIXME: directory permission is not set
649 if os.path.isdir(os.path.join(target_dir, fname)):
650 shutil.rmtree(os.path.join(target_dir, fname))
651 shutil.copytree(fname, target_dir)
653 # env.AddPostAction(File(i), env.Chmod(i, perm))
656 def env_installProg(target, source, env):
657 installPerm(target, source, env, 0755)
659 def env_installFile(target, source, env):
660 installPerm(target, source, env, 0644)
662 ## def DistSources(env, node):
663 ## env.DistFiles(_get_sources(env, node))
665 ## def DistFiles(env, files):
666 ## assert isinstance(files, (list, tuple))
667 ## DISTFILES = [env.File(fname) for fname in files]
668 ## env.AppendUnique(DISTFILES=DISTFILES)
671 ## def make_distdir(target=None, source=None, env=None):
672 ## distdir = env.subst('$DISTDIR')
673 ## Execute(Delete(distdir))
674 ## Execute(Mkdir(distdir))
675 ## for fnode in env["DISTFILES"]:
676 ## dirname, fname = os.path.split(str(fnode))
678 ## distdirname = os.path.join(distdir, dirname)
679 ## if not os.path.exists(distdirname):
680 ## Execute(Mkdir(distdirname))
681 ## Execute(Copy(os.path.join(distdir, dirname, fname), str(fnode)))
683 ## def make_dist(target=None, source=None, env=None):
684 ## return Popen([env['TAR'], "-zcf",
685 ## env.subst("${PACKAGE}-${VERSION}.tar.gz"),
686 ## env.subst('$DISTDIR')]).wait()
688 ## def make_distcheck(target=None, source=None, env=None):
689 ## distdir = env.subst('$DISTDIR')
690 ## distcheckinstdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-instdir-'))
691 ## distcheckdestdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-destdir-'))
692 ## instdirs = [os.path.join(distcheckinstdir, d) for d in
693 ## 'lib', 'share', 'bin', 'include']
694 ## for dir_ in instdirs:
695 ## Execute(Mkdir(dir_))
697 ## cmd = env.subst("cd $DISTDIR && scons DESTDIR=%s prefix=%s"
698 ## " && scons check && scons install") %\
699 ## (os.path.join(distcheckdestdir, ''), distcheckinstdir)
700 ## status = Popen(cmd, shell=True).wait()
703 ## ## Check that inst dirs are empty (to catch cases of $DESTDIR not being honored
704 ## for dir_ in instdirs:
705 ## if os.listdir(dir_):
706 ## raise SCons.Errors.BuildError(target, "%s not empy" % dir_)
707 ## ## Check that something inside $DESTDIR was installed
708 ## dir_ = os.path.join(distcheckdestdir, distcheckinstdir)
709 ## if not os.path.exists(dir_):
710 ## raise SCons.Errors.BuildError(target, "%s does not exist" % dir_)
711 ## Execute(Delete(distcheckinstdir))
712 ## Execute(Delete(distcheckdestdir))
713 ## Execute(Delete(distdir))
715 ## def InstallWithDestDir(self, dir_, source):
716 ## dir_ = '${DESTDIR}' + str(dir_)
717 ## return SConsEnvironment.Install(self, dir_, source)
720 ## def InstallAsWithDestDir(self, target, source):
721 ## target = '${DESTDIR}' + str(target)
722 ## return SConsEnvironment.InstallAs(self, target, source)
724 ## def generate(env):
725 ## env.EnsureSConsVersion(0, 96, 91)
727 ## opts = Options(['options.cache'], ARGUMENTS)
728 ## opts.Add(PathOption('prefix', 'Installation prefix', '/usr/local'))
729 ## opts.Add(PathOption('exec_prefix', 'Installation prefix blah blah',
731 ## opts.Add(PathOption('libdir',
732 ## 'Installation prefix for architecture dependent files', '$prefix/lib'))
733 ## opts.Add(PathOption('includedir',
734 ## 'Installation prefix for C header files', '$prefix/include'))
735 ## opts.Add(PathOption('datadir',
736 ## 'Installation prefix for architecture independent files', '$prefix/share'))
737 ## opts.Add(PathOption('bindir', 'Installation prefix for programs', '$prefix/bin'))
738 ## opts.Add(PathOption('DESTDIR', 'blah blah', None))
740 ## opts.Save('options.cache', env)
741 ## SConsEnvironment.Help(env, opts.GenerateHelpText(env))
743 ## env.Append(CPPFLAGS=r' -DVERSION=\"$VERSION\"')
744 ## env.Append(CCFLAGS=ARGUMENTS.get('CCFLAGS', '-g -O2'))
746 ## env['GNOME_TESTS'] = dict(CheckPython=CheckPython,
747 ## CheckPythonHeaders=CheckPythonHeaders,
748 ## PkgCheckModules=PkgCheckModules)
750 ## SConsEnvironment.DistSources = DistSources
751 ## SConsEnvironment.DistFiles = DistFiles
752 ## env['DISTDIR'] = "${PACKAGE}-${VERSION}"
754 ## #env.Command(env.Dir("$DISTDIR"), None, make_distdir)
756 ## distdir_alias = env.Alias("distdir", None, make_distdir)
757 ## dist_alias = env.Alias("dist", None, make_dist)
758 ## env.Depends(dist_alias, distdir_alias)
759 ## distcheck_alias = env.Alias("distcheck", None, make_distcheck)
760 ## env.Depends(distcheck_alias, distdir_alias)
761 ## env.AlwaysBuild(env.Alias('check'))
763 ## #env['TARFLAGS'] ='-c -z'
764 ## #env['TARSUFFIX'] = '.tar.gz'
765 ## #tar = env.Tar('${PACKAGE}-${VERSION}.tar.gz', "${DISTDIR}")
766 ## #env.Depends(tar, distdir_alias)
767 ## #print env['DEFAULT_TARGETS']
769 ## #env.Depends(distdir_alias, "${DISTFILES}")
770 ## #env.Alias('dist', tar)
771 ## env.AlwaysBuild('dist')
772 ## env.AlwaysBuild('distdir')
773 ## env.AlwaysBuild('distcheck')
774 ## env.DistFiles(['SConstruct', 'scons/gnome.py'])
776 ## env['BUILDERS']['EnvSubstFile'] = SCons.Builder.Builder(action=env_subst)
778 ## SConsEnvironment.PythonByteCompile = env.Action(byte_compile_python)
780 ## env.Install = new.instancemethod(InstallWithDestDir, env, env.__class__)
781 ## env.InstallAs = new.instancemethod(InstallAsWithDestDir, env, env.__class__)