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 ''' target can be a directory '''
76 shutil.copy(str(source[0]), str(target[0]))
83 def checkPkgConfig(conf, version):
84 ''' Return false if pkg_config does not exist, or is too old '''
85 conf.Message('Checking for pkg-config...')
86 ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
91 def checkPackage(conf, pkg):
92 ''' check if pkg is under the control of conf '''
93 conf.Message('Checking for package %s...' % pkg)
94 ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
99 def startConfigH(top_src_dir):
100 ''' Write the first part of config.h '''
101 writeToFile(os.path.join(top_src_dir, config_h),
102 '''/* src/config.h. Generated by scon. */
107 * This file is part of LyX, the document processor.
108 * Licence details can be found in the file COPYING.
110 * This is the compilation configuration file for LyX.
111 * It was generated by scon.
112 * You might want to change some of the defaults if something goes wrong
113 * during the compilation.
121 def endConfigH(top_src_dir):
122 ''' Write the last part of config.h '''
123 writeToFile(os.path.join(top_src_dir, config_h), '''
124 /************************************************************
125 ** You should not need to change anything beyond this point */
127 #ifndef HAVE_STRERROR
128 #if defined(__cplusplus)
131 char * strerror(int n);
135 #ifndef HAVE_DECL_MKSTEMP
136 #if defined(__cplusplus)
143 #if defined(HAVE_OSTREAM) && defined(HAVE_LOCALE) && defined(HAVE_SSTREAM)
144 # define USE_BOOST_FORMAT 1
146 # define USE_BOOST_FORMAT 0
149 #define BOOST_USER_CONFIG <config.h>
151 #if !defined(ENABLE_ASSERTIONS)
152 # define BOOST_DISABLE_ASSERTS 1
154 #define BOOST_ENABLE_ASSERT_HANDLER 1
156 #define BOOST_DISABLE_THREADS 1
157 #define BOOST_NO_WREGEX 1
158 #define BOOST_NO_WSTRING 1
161 # define BOOST_POSIX 1
164 #if defined(HAVE_NEWAPIS_H)
165 # define WANT_GETFILEATTRIBUTESEX_WRAPPER 1
173 def checkPutenv(conf):
174 check_putenv_source = """
182 conf.Message('Checking for putenv... ')
183 ret = conf.TryLink(check_putenv_source, '.c')
188 #HAVE_DECL_ISTREAMBUF_ITERATOR
189 def checkIstreambufIterator(conf):
190 check_istreambuf_iterator_source = """
195 std::istreambuf_iterator<std::istream> iter;
199 conf.Message('Checking for iostreambuf::iterator... ')
200 ret = conf.TryLink(check_istreambuf_iterator_source, '.cpp')
206 def checkMkdirOneArg(conf):
207 check_mkdir_one_arg_source = """
208 #include <sys/stat.h>
215 check_mkdir_one_arg_source2 = """
223 conf.Message('Checking for the number of args for mkdir... ')
224 ret = conf.TryLink(check_mkdir_one_arg_source, '.c')
228 ret = conf.TryLink(check_mkdir_one_arg_source2, '.c')
237 def checkStdCount(conf):
238 check_std_count_source = """
241 int countChar(char * b, char * e, char const c)
243 return count(b, e, c);
249 int i = countChar(a, a + 5, 'l');
252 conf.Message('Checking for std::count... ')
253 ret = conf.TryLink(check_std_count_source, '.cpp')
261 def checkSelectArgType(conf):
262 ''' Adapted from autoconf '''
263 conf.Message('Checking for arg types for select... ')
264 for arg234 in ['fd_set *', 'int *', 'void *']:
265 for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
266 for arg5 in ['struct timeval *', 'const struct timeval *']:
267 check_select_source = '''
268 #if HAVE_SYS_SELECT_H
269 # include <sys/select.h>
271 #if HAVE_SYS_SOCKET_H
272 # include <sys/socket.h>
274 extern int select (%s, %s, %s, %s, %s);
279 ''' % (arg1, arg234, arg234, arg234, arg5)
280 ret = conf.TryLink(check_select_source, '.c')
283 return (arg1, arg234, arg5)
284 conf.Result('no (use default)')
285 return ('int', 'int *', 'struct timeval *')
288 def checkBoostLibraries(conf, lib, pathes):
289 ''' look for boost libraries '''
290 conf.Message('Checking for boost library %s... ' % lib)
292 # direct form: e.g. libboost_iostreams.a
293 if os.path.isfile(os.path.join(path, 'lib%s.a' % lib)):
296 # check things like libboost_iostreams-gcc.a
297 files = glob.glob(os.path.join(path, 'lib%s-*.a' % lib))
298 # if there are more than one, choose the first one
299 # FIXME: choose the best one.
301 # get xxx-gcc from /usr/local/lib/libboost_xxx-gcc.a
303 return (path, files[0].split(os.sep)[-1][3:-2])
308 def checkMsgFmt(conf):
309 ''' check the existence of command msgfmt '''
310 conf.Message('Checking for gettext command msgfmt...')
311 res = conf.TryAction('msgfmt --help')
319 def installCygwinLDScript(path):
320 ''' Install i386pe.x-no-rdata '''
321 ld_script = os.path.join(path, 'i386pe.x-no-rdata')
322 script = open(ld_script, 'w')
323 script.write('''/* specific linker script avoiding .rdata sections, for normal executables
325 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
326 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
328 OUTPUT_FORMAT(pei-i386)
329 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
330 ENTRY(_mainCRTStartup)
333 .text __image_base__ + __section_alignment__ :
340 ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
341 LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
342 ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
343 LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
345 /* ??? Why is .gcc_exc here? */
350 /* The Cygwin32 library uses a section to avoid copying certain data
351 on fork. This used to be named ".data". The linker used
352 to include this between __data_start__ and __data_end__, but that
353 breaks building the cygwin32 dll. Instead, we name the section
354 ".data_cygwin_nocopy" and explictly include it after __data_end__. */
355 .data BLOCK(__section_alignment__) :
364 ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
365 __RUNTIME_PSEUDO_RELOC_LIST__ = .;
366 *(.rdata_runtime_pseudo_reloc)
367 ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
368 __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
370 *(.data_cygwin_nocopy)
372 .rdata BLOCK(__section_alignment__) :
375 .pdata BLOCK(__section_alignment__) :
379 .bss BLOCK(__section_alignment__) :
386 .edata BLOCK(__section_alignment__) :
397 .idata BLOCK(__section_alignment__) :
399 /* This cannot currently be handled with grouped sections.
400 See pe.em:sort_sections. */
403 /* These zeroes mark the end of the import list. */
404 LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
410 .CRT BLOCK(__section_alignment__) :
412 ___crt_xc_start__ = . ;
413 *(SORT(.CRT$XC*)) /* C initialization */
414 ___crt_xc_end__ = . ;
415 ___crt_xi_start__ = . ;
416 *(SORT(.CRT$XI*)) /* C++ initialization */
417 ___crt_xi_end__ = . ;
418 ___crt_xl_start__ = . ;
419 *(SORT(.CRT$XL*)) /* TLS callbacks */
420 /* ___crt_xl_end__ is defined in the TLS Directory support code */
421 ___crt_xp_start__ = . ;
422 *(SORT(.CRT$XP*)) /* Pre-termination */
423 ___crt_xp_end__ = . ;
424 ___crt_xt_start__ = . ;
425 *(SORT(.CRT$XT*)) /* Termination */
426 ___crt_xt_end__ = . ;
428 .tls BLOCK(__section_alignment__) :
436 .endjunk BLOCK(__section_alignment__) :
438 /* end is deprecated, don't use it */
443 .rsrc BLOCK(__section_alignment__) :
448 .reloc BLOCK(__section_alignment__) :
452 .stab BLOCK(__section_alignment__) (NOLOAD) :
456 .stabstr BLOCK(__section_alignment__) (NOLOAD) :
460 /* DWARF debug sections.
461 Symbols in the DWARF debugging sections are relative to the beginning
462 of the section. Unlike other targets that fake this by putting the
463 section VMA at 0, the PE format will not allow it. */
464 /* DWARF 1.1 and DWARF 2. */
465 .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
469 .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
474 .debug_info BLOCK(__section_alignment__) (NOLOAD) :
476 *(.debug_info) *(.gnu.linkonce.wi.*)
478 .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
482 .debug_line BLOCK(__section_alignment__) (NOLOAD) :
486 .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
490 .debug_str BLOCK(__section_alignment__) (NOLOAD) :
494 .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
498 .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
502 /* SGI/MIPS DWARF 2 extensions. */
503 .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
507 .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
511 .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
515 .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
520 .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
531 # these will be used under win32
537 # does not matter if it fails on other systems
542 def __init__(self, env, logfile, longarg, info):
543 # save the spawn system
545 self.logfile = logfile
546 # clear the logfile (it may not exist)
548 # this will overwrite existing content.
549 writeToFile(logfile, info, append=False)
551 self.longarg = longarg
552 # get hold of the old spawn? (necessary?)
553 self._spawn = env['SPAWN']
556 def spawn(self, sh, escape, cmd, args, spawnenv):
558 newargs = ' '.join(map(escape, args[1:]))
559 cmdline = cmd + " " + newargs
561 # if log is not empty, write to it
562 if self.logfile != '':
563 # this tend to be slow (?) but ensure correct output
564 # Note that cmdline may be long so I do not escape it
566 # since this is not an essential operation, proceed if things go wrong here.
567 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
569 print "Warning: can not write to log file ", self.logfile
571 # if the command is not too long, use the old
572 if not self.longarg or len(cmdline) < 8000:
573 exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
575 sAttrs = win32security.SECURITY_ATTRIBUTES()
576 StartupInfo = win32process.STARTUPINFO()
578 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
579 # check for any special operating system commands
582 win32file.DeleteFile(arg)
585 # otherwise execute the command.
586 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
587 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
588 exit_code = win32process.GetExitCodeProcess(hProcess)
589 win32file.CloseHandle(hProcess);
590 win32file.CloseHandle(hThread);
594 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
595 ''' This function modify env and allow logging of
596 commands to a logfile. If the argument is too long
597 a win32 spawn will be used instead of the system one
600 # create a new spwn object
601 ls = loggedSpawn(env, logfile, longarg, info)
602 # replace the old SPAWN by the new function
603 env['SPAWN'] = ls.spawn
606 ## def DistSources(env, node):
607 ## env.DistFiles(_get_sources(env, node))
609 ## def DistFiles(env, files):
610 ## assert isinstance(files, (list, tuple))
611 ## DISTFILES = [env.File(fname) for fname in files]
612 ## env.AppendUnique(DISTFILES=DISTFILES)
615 ## def make_distdir(target=None, source=None, env=None):
616 ## distdir = env.subst('$DISTDIR')
617 ## Execute(Delete(distdir))
618 ## Execute(Mkdir(distdir))
619 ## for fnode in env["DISTFILES"]:
620 ## dirname, fname = os.path.split(str(fnode))
622 ## distdirname = os.path.join(distdir, dirname)
623 ## if not os.path.exists(distdirname):
624 ## Execute(Mkdir(distdirname))
625 ## Execute(Copy(os.path.join(distdir, dirname, fname), str(fnode)))
627 ## def make_dist(target=None, source=None, env=None):
628 ## return Popen([env['TAR'], "-zcf",
629 ## env.subst("${PACKAGE}-${VERSION}.tar.gz"),
630 ## env.subst('$DISTDIR')]).wait()
632 ## def make_distcheck(target=None, source=None, env=None):
633 ## distdir = env.subst('$DISTDIR')
634 ## distcheckinstdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-instdir-'))
635 ## distcheckdestdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-destdir-'))
636 ## instdirs = [os.path.join(distcheckinstdir, d) for d in
637 ## 'lib', 'share', 'bin', 'include']
638 ## for dir_ in instdirs:
639 ## Execute(Mkdir(dir_))
641 ## cmd = env.subst("cd $DISTDIR && scons DESTDIR=%s prefix=%s"
642 ## " && scons check && scons install") %\
643 ## (os.path.join(distcheckdestdir, ''), distcheckinstdir)
644 ## status = Popen(cmd, shell=True).wait()
647 ## ## Check that inst dirs are empty (to catch cases of $DESTDIR not being honored
648 ## for dir_ in instdirs:
649 ## if os.listdir(dir_):
650 ## raise SCons.Errors.BuildError(target, "%s not empy" % dir_)
651 ## ## Check that something inside $DESTDIR was installed
652 ## dir_ = os.path.join(distcheckdestdir, distcheckinstdir)
653 ## if not os.path.exists(dir_):
654 ## raise SCons.Errors.BuildError(target, "%s does not exist" % dir_)
655 ## Execute(Delete(distcheckinstdir))
656 ## Execute(Delete(distcheckdestdir))
657 ## Execute(Delete(distdir))
659 ## def InstallWithDestDir(self, dir_, source):
660 ## dir_ = '${DESTDIR}' + str(dir_)
661 ## return SConsEnvironment.Install(self, dir_, source)
664 ## def InstallAsWithDestDir(self, target, source):
665 ## target = '${DESTDIR}' + str(target)
666 ## return SConsEnvironment.InstallAs(self, target, source)
668 ## def generate(env):
669 ## env.EnsureSConsVersion(0, 96, 91)
671 ## opts = Options(['options.cache'], ARGUMENTS)
672 ## opts.Add(PathOption('prefix', 'Installation prefix', '/usr/local'))
673 ## opts.Add(PathOption('exec_prefix', 'Installation prefix blah blah',
675 ## opts.Add(PathOption('libdir',
676 ## 'Installation prefix for architecture dependent files', '$prefix/lib'))
677 ## opts.Add(PathOption('includedir',
678 ## 'Installation prefix for C header files', '$prefix/include'))
679 ## opts.Add(PathOption('datadir',
680 ## 'Installation prefix for architecture independent files', '$prefix/share'))
681 ## opts.Add(PathOption('bindir', 'Installation prefix for programs', '$prefix/bin'))
682 ## opts.Add(PathOption('DESTDIR', 'blah blah', None))
684 ## opts.Save('options.cache', env)
685 ## SConsEnvironment.Help(env, opts.GenerateHelpText(env))
687 ## env.Append(CPPFLAGS=r' -DVERSION=\"$VERSION\"')
688 ## env.Append(CCFLAGS=ARGUMENTS.get('CCFLAGS', '-g -O2'))
690 ## env['GNOME_TESTS'] = dict(CheckPython=CheckPython,
691 ## CheckPythonHeaders=CheckPythonHeaders,
692 ## PkgCheckModules=PkgCheckModules)
694 ## SConsEnvironment.DistSources = DistSources
695 ## SConsEnvironment.DistFiles = DistFiles
696 ## env['DISTDIR'] = "${PACKAGE}-${VERSION}"
698 ## #env.Command(env.Dir("$DISTDIR"), None, make_distdir)
700 ## distdir_alias = env.Alias("distdir", None, make_distdir)
701 ## dist_alias = env.Alias("dist", None, make_dist)
702 ## env.Depends(dist_alias, distdir_alias)
703 ## distcheck_alias = env.Alias("distcheck", None, make_distcheck)
704 ## env.Depends(distcheck_alias, distdir_alias)
705 ## env.AlwaysBuild(env.Alias('check'))
707 ## #env['TARFLAGS'] ='-c -z'
708 ## #env['TARSUFFIX'] = '.tar.gz'
709 ## #tar = env.Tar('${PACKAGE}-${VERSION}.tar.gz', "${DISTDIR}")
710 ## #env.Depends(tar, distdir_alias)
711 ## #print env['DEFAULT_TARGETS']
713 ## #env.Depends(distdir_alias, "${DISTFILES}")
714 ## #env.Alias('dist', tar)
715 ## env.AlwaysBuild('dist')
716 ## env.AlwaysBuild('distdir')
717 ## env.AlwaysBuild('distcheck')
718 ## env.DistFiles(['SConstruct', 'scons/gnome.py'])
720 ## env['BUILDERS']['EnvSubstFile'] = SCons.Builder.Builder(action=env_subst)
722 ## SConsEnvironment.PythonByteCompile = env.Action(byte_compile_python)
724 ## env.Install = new.instancemethod(InstallWithDestDir, env, env.__class__)
725 ## env.InstallAs = new.instancemethod(InstallAsWithDestDir, env, env.__class__)