lyx_mirror/development/scons/scons_utils.py
Bo Peng 44d2042c9a Scons: initial msvc support, and separate fast_start and load_option options.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13962 a592a061-630c-0410-9148-cb99ea01b6c8
2006-05-29 16:58:14 +00:00

685 lines
20 KiB
Python

# vi:filetype=python:expandtab:tabstop=2:shiftwidth=2
#
# file scons_utils.py
#
# This file is part of LyX, the document processor.
# Licence details can be found in the file COPYING.
#
# \author Bo Peng
# Full author contact details are available in file CREDITS.
#
# This file defines all the utility functions for the
# scons-based build system of lyx
#
import os, sys, re, shutil, glob
from SCons.Util import WhereIs
config_h = os.path.join('src', 'config.h')
config_content = ''
def writeToFile(filename, lines, append = False):
" utility function: write or append lines to filename "
if append:
file = open(filename, 'a')
else:
file = open(filename, 'w')
file.write(lines)
file.close()
def printEnvironment(env, keys=[]):
''' used to check profile settings '''
dict = env.Dictionary()
if len(keys) == 0:
keys = dict.keys()
keys.sort()
for key in keys:
try:
# try to expand, but this is not always possible
print key, '=', env.subst('$'+key)
except:
print '<<UNEXPANDED>>:', key, '=', dict[key]
def env_subst(target, source, env):
''' subst variables in source by those in env, and output to target
source and target are scons File() objects
%key% (not key itself) is an indication of substitution
'''
assert len(target) == 1
assert len(source) == 1
target_file = file(str(target[0]), "w")
source_file = file(str(source[0]), "r")
contents = source_file.read()
for k, v in env.items():
try:
val = env.subst('$'+k)
# temporary fix for the \Resource backslash problem
val = val.replace('\\', '/')
# multi-line replacement
val = val.replace('\n',r'\\n\\\n')
contents = re.sub('@'+k+'@', val, contents)
contents = re.sub('%'+k+'%', val, contents)
except:
pass
target_file.write(contents + "\n")
target_file.close()
#st = os.stat(str(source[0]))
#os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
#
# autoconf tests
#
def checkPkgConfig(conf, version):
''' Return false if pkg_config does not exist, or is too old '''
conf.Message('Checking for pkg-config...')
ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
conf.Result(ret)
return ret
def checkPackage(conf, pkg):
''' check if pkg is under the control of conf '''
conf.Message('Checking for package %s...' % pkg)
ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
conf.Result(ret)
return ret
def startConfigH():
''' Write the first part of config.h '''
global config_content
config_content = '''/* src/config.h. Generated by scon. */
/* -*- C++ -*- */
/*
* \file config.h
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* This is the compilation configuration file for LyX.
* It was generated by scon.
* You might want to change some of the defaults if something goes wrong
* during the compilation.
*/
#ifndef _CONFIG_H
#define _CONFIG_H
'''
def addToConfig(lines, newline=2):
''' utility function: shortcut for appending lines to outfile
add newline at the end of lines.
'''
global config_content
if lines.strip() != '':
config_content += lines + '\n'*newline
def endConfigH(top_src_dir):
''' Write the last part of config.h '''
global config_content
writeToFile(os.path.join(top_src_dir, config_h), config_content +
'''/************************************************************
** You should not need to change anything beyond this point */
#ifndef HAVE_STRERROR
#if defined(__cplusplus)
extern "C"
#endif
char * strerror(int n);
#endif
#ifdef HAVE_MKSTEMP
#ifndef HAVE_DECL_MKSTEMP
#if defined(__cplusplus)
extern "C"
#endif
int mkstemp(char*);
#endif
#endif
#if defined(HAVE_OSTREAM) && defined(HAVE_LOCALE) && defined(HAVE_SSTREAM)
# define USE_BOOST_FORMAT 1
#else
# define USE_BOOST_FORMAT 0
#endif
#define BOOST_USER_CONFIG <config.h>
#if !defined(ENABLE_ASSERTIONS)
# define BOOST_DISABLE_ASSERTS 1
#endif
#define BOOST_ENABLE_ASSERT_HANDLER 1
#define BOOST_DISABLE_THREADS 1
#define BOOST_NO_WREGEX 1
#define BOOST_NO_WSTRING 1
#ifdef __CYGWIN__
# define BOOST_POSIX 1
#endif
#if defined(HAVE_NEWAPIS_H)
# define WANT_GETFILEATTRIBUTESEX_WRAPPER 1
#endif
#endif
''')
#MKDIR_TAKES_ONE_ARG
def checkMkdirOneArg(conf):
check_mkdir_one_arg_source = """
#include <sys/stat.h>
int main()
{
mkdir("somedir");
}
"""
conf.Message('Checking for the number of args for mkdir... ')
ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
if ret:
conf.Result('one')
else:
conf.Result('two')
return ret
# CXX_GLOBAL_CSTD
def checkCXXGlobalCstd(conf):
''' Check the use of std::tolower or tolower '''
check_global_cstd_source = '''
#include <cctype>
using std::tolower;
int main()
{
return 0;
}
'''
conf.Message('Check for the use of global cstd... ')
ret = conf.TryLink(check_global_cstd_source, '.c')
conf.Result(ret)
return ret
# SELECT_TYPE_ARG1
# SELECT_TYPE_ARG234
# SELECT_TYPE_ARG5
def checkSelectArgType(conf):
''' Adapted from autoconf '''
conf.Message('Checking for arg types for select... ')
for arg234 in ['fd_set *', 'int *', 'void *']:
for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
for arg5 in ['struct timeval *', 'const struct timeval *']:
check_select_source = '''
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#if HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
extern int select (%s, %s, %s, %s, %s);
int main()
{
return(0);
}
''' % (arg1, arg234, arg234, arg234, arg5)
ret = conf.TryLink(check_select_source, '.c')
if ret:
conf.Result(ret)
return (arg1, arg234, arg5)
conf.Result('no (use default)')
return ('int', 'int *', 'struct timeval *')
def checkBoostLibraries(conf, lib, pathes):
''' look for boost libraries '''
conf.Message('Checking for boost library %s... ' % lib)
for path in pathes:
# direct form: e.g. libboost_iostreams.a
if os.path.isfile(os.path.join(path, 'lib%s.a' % lib)):
conf.Result('yes')
return (path, lib)
# check things like libboost_iostreams-gcc.a
files = glob.glob(os.path.join(path, 'lib%s-*.a' % lib))
# if there are more than one, choose the first one
# FIXME: choose the best one.
if len(files) >= 1:
# get xxx-gcc from /usr/local/lib/libboost_xxx-gcc.a
conf.Result('yes')
return (path, files[0].split(os.sep)[-1][3:-2])
conf.Result('n')
return ('','')
def checkCommand(conf, cmd):
''' check the existence of a command
return full path to the command, or none
'''
conf.Message('Checking for command %s...' % cmd)
res = WhereIs(cmd)
conf.Result(res is not None)
return res
def installCygwinLDScript(path):
''' Install i386pe.x-no-rdata '''
ld_script = os.path.join(path, 'i386pe.x-no-rdata')
script = open(ld_script, 'w')
script.write('''/* specific linker script avoiding .rdata sections, for normal executables
for a reference see
http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
*/
OUTPUT_FORMAT(pei-i386)
SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
ENTRY(_mainCRTStartup)
SECTIONS
{
.text __image_base__ + __section_alignment__ :
{
*(.init)
*(.text)
*(SORT(.text$*))
*(.glue_7t)
*(.glue_7)
___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*)); LONG (0);
___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*)); LONG (0);
*(.fini)
/* ??? Why is .gcc_exc here? */
*(.gcc_exc)
PROVIDE (etext = .);
*(.gcc_except_table)
}
/* The Cygwin32 library uses a section to avoid copying certain data
on fork. This used to be named ".data". The linker used
to include this between __data_start__ and __data_end__, but that
breaks building the cygwin32 dll. Instead, we name the section
".data_cygwin_nocopy" and explictly include it after __data_end__. */
.data BLOCK(__section_alignment__) :
{
__data_start__ = . ;
*(.data)
*(.data2)
*(SORT(.data$*))
*(.rdata)
*(SORT(.rdata$*))
*(.eh_frame)
___RUNTIME_PSEUDO_RELOC_LIST__ = .;
__RUNTIME_PSEUDO_RELOC_LIST__ = .;
*(.rdata_runtime_pseudo_reloc)
___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
__RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
__data_end__ = . ;
*(.data_cygwin_nocopy)
}
.rdata BLOCK(__section_alignment__) :
{
}
.pdata BLOCK(__section_alignment__) :
{
*(.pdata)
}
.bss BLOCK(__section_alignment__) :
{
__bss_start__ = . ;
*(.bss)
*(COMMON)
__bss_end__ = . ;
}
.edata BLOCK(__section_alignment__) :
{
*(.edata)
}
/DISCARD/ :
{
*(.debug$S)
*(.debug$T)
*(.debug$F)
*(.drectve)
}
.idata BLOCK(__section_alignment__) :
{
/* This cannot currently be handled with grouped sections.
See pe.em:sort_sections. */
SORT(*)(.idata$2)
SORT(*)(.idata$3)
/* These zeroes mark the end of the import list. */
LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
SORT(*)(.idata$4)
SORT(*)(.idata$5)
SORT(*)(.idata$6)
SORT(*)(.idata$7)
}
.CRT BLOCK(__section_alignment__) :
{
___crt_xc_start__ = . ;
*(SORT(.CRT$XC*)) /* C initialization */
___crt_xc_end__ = . ;
___crt_xi_start__ = . ;
*(SORT(.CRT$XI*)) /* C++ initialization */
___crt_xi_end__ = . ;
___crt_xl_start__ = . ;
*(SORT(.CRT$XL*)) /* TLS callbacks */
/* ___crt_xl_end__ is defined in the TLS Directory support code */
___crt_xp_start__ = . ;
*(SORT(.CRT$XP*)) /* Pre-termination */
___crt_xp_end__ = . ;
___crt_xt_start__ = . ;
*(SORT(.CRT$XT*)) /* Termination */
___crt_xt_end__ = . ;
}
.tls BLOCK(__section_alignment__) :
{
___tls_start__ = . ;
*(.tls)
*(.tls$)
*(SORT(.tls$*))
___tls_end__ = . ;
}
.endjunk BLOCK(__section_alignment__) :
{
/* end is deprecated, don't use it */
PROVIDE (end = .);
PROVIDE ( _end = .);
__end__ = .;
}
.rsrc BLOCK(__section_alignment__) :
{
*(.rsrc)
*(SORT(.rsrc$*))
}
.reloc BLOCK(__section_alignment__) :
{
*(.reloc)
}
.stab BLOCK(__section_alignment__) (NOLOAD) :
{
*(.stab)
}
.stabstr BLOCK(__section_alignment__) (NOLOAD) :
{
*(.stabstr)
}
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section. Unlike other targets that fake this by putting the
section VMA at 0, the PE format will not allow it. */
/* DWARF 1.1 and DWARF 2. */
.debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_aranges)
}
.debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_pubnames)
}
/* DWARF 2. */
.debug_info BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_info) *(.gnu.linkonce.wi.*)
}
.debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_abbrev)
}
.debug_line BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_line)
}
.debug_frame BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_frame)
}
.debug_str BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_str)
}
.debug_loc BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_loc)
}
.debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_macinfo)
}
/* SGI/MIPS DWARF 2 extensions. */
.debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_weaknames)
}
.debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_funcnames)
}
.debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_typenames)
}
.debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_varnames)
}
/* DWARF 3. */
.debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
{
*(.debug_ranges)
}
}
''')
script.close()
return(ld_script)
try:
# these will be used under win32
import win32file
import win32event
import win32process
import win32security
except:
# does not matter if it fails on other systems
pass
class loggedSpawn:
def __init__(self, env, logfile, longarg, info):
# save the spawn system
self.env = env
self.logfile = logfile
# clear the logfile (it may not exist)
if logfile != '':
# this will overwrite existing content.
writeToFile(logfile, info, append=False)
#
self.longarg = longarg
# get hold of the old spawn? (necessary?)
self._spawn = env['SPAWN']
# define new SPAWN
def spawn(self, sh, escape, cmd, args, spawnenv):
# get command line
newargs = ' '.join(map(escape, args[1:]))
cmdline = cmd + " " + newargs
#
# if log is not empty, write to it
if self.logfile != '':
# this tend to be slow (?) but ensure correct output
# Note that cmdline may be long so I do not escape it
try:
# since this is not an essential operation, proceed if things go wrong here.
writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
except:
print "Warning: can not write to log file ", self.logfile
#
# if the command is not too long, use the old
if not self.longarg or len(cmdline) < 8000:
exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
else:
sAttrs = win32security.SECURITY_ATTRIBUTES()
StartupInfo = win32process.STARTUPINFO()
for var in spawnenv:
spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
# check for any special operating system commands
if cmd == 'del':
for arg in args[1:]:
win32file.DeleteFile(arg)
exit_code = 0
else:
# otherwise execute the command.
hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
exit_code = win32process.GetExitCodeProcess(hProcess)
win32file.CloseHandle(hProcess);
win32file.CloseHandle(hThread);
return exit_code
def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
''' This function modify env and allow logging of
commands to a logfile. If the argument is too long
a win32 spawn will be used instead of the system one
'''
#
# create a new spwn object
ls = loggedSpawn(env, logfile, longarg, info)
# replace the old SPAWN by the new function
env['SPAWN'] = ls.spawn
## def DistSources(env, node):
## env.DistFiles(_get_sources(env, node))
##
## def DistFiles(env, files):
## assert isinstance(files, (list, tuple))
## DISTFILES = [env.File(fname) for fname in files]
## env.AppendUnique(DISTFILES=DISTFILES)
##
##
## def make_distdir(target=None, source=None, env=None):
## distdir = env.subst('$DISTDIR')
## Execute(Delete(distdir))
## Execute(Mkdir(distdir))
## for fnode in env["DISTFILES"]:
## dirname, fname = os.path.split(str(fnode))
## if dirname:
## distdirname = os.path.join(distdir, dirname)
## if not os.path.exists(distdirname):
## Execute(Mkdir(distdirname))
## Execute(Copy(os.path.join(distdir, dirname, fname), str(fnode)))
##
## def make_dist(target=None, source=None, env=None):
## return Popen([env['TAR'], "-zcf",
## env.subst("${PACKAGE}-${VERSION}.tar.gz"),
## env.subst('$DISTDIR')]).wait()
##
## def make_distcheck(target=None, source=None, env=None):
## distdir = env.subst('$DISTDIR')
## distcheckinstdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-instdir-'))
## distcheckdestdir = tempfile.mkdtemp('', env.subst('${PACKAGE}-${VERSION}-destdir-'))
## instdirs = [os.path.join(distcheckinstdir, d) for d in
## 'lib', 'share', 'bin', 'include']
## for dir_ in instdirs:
## Execute(Mkdir(dir_))
##
## cmd = env.subst("cd $DISTDIR && scons DESTDIR=%s prefix=%s"
## " && scons check && scons install") %\
## (os.path.join(distcheckdestdir, ''), distcheckinstdir)
## status = Popen(cmd, shell=True).wait()
## if status:
## return status
## ## Check that inst dirs are empty (to catch cases of $DESTDIR not being honored
## for dir_ in instdirs:
## if os.listdir(dir_):
## raise SCons.Errors.BuildError(target, "%s not empy" % dir_)
## ## Check that something inside $DESTDIR was installed
## dir_ = os.path.join(distcheckdestdir, distcheckinstdir)
## if not os.path.exists(dir_):
## raise SCons.Errors.BuildError(target, "%s does not exist" % dir_)
## Execute(Delete(distcheckinstdir))
## Execute(Delete(distcheckdestdir))
## Execute(Delete(distdir))
##
## def InstallWithDestDir(self, dir_, source):
## dir_ = '${DESTDIR}' + str(dir_)
## return SConsEnvironment.Install(self, dir_, source)
##
##
## def InstallAsWithDestDir(self, target, source):
## target = '${DESTDIR}' + str(target)
## return SConsEnvironment.InstallAs(self, target, source)
##
## def generate(env):
## env.EnsureSConsVersion(0, 96, 91)
##
## opts = Options(['options.cache'], ARGUMENTS)
## opts.Add(PathOption('prefix', 'Installation prefix', '/usr/local'))
## opts.Add(PathOption('exec_prefix', 'Installation prefix blah blah',
## '$prefix'))
## opts.Add(PathOption('libdir',
## 'Installation prefix for architecture dependent files', '$prefix/lib'))
## opts.Add(PathOption('includedir',
## 'Installation prefix for C header files', '$prefix/include'))
## opts.Add(PathOption('datadir',
## 'Installation prefix for architecture independent files', '$prefix/share'))
## opts.Add(PathOption('bindir', 'Installation prefix for programs', '$prefix/bin'))
## opts.Add(PathOption('DESTDIR', 'blah blah', None))
## opts.Update(env)
## opts.Save('options.cache', env)
## SConsEnvironment.Help(env, opts.GenerateHelpText(env))
##
## env.Append(CPPFLAGS=r' -DVERSION=\"$VERSION\"')
## env.Append(CCFLAGS=ARGUMENTS.get('CCFLAGS', '-g -O2'))
##
## env['GNOME_TESTS'] = dict(CheckPython=CheckPython,
## CheckPythonHeaders=CheckPythonHeaders,
## PkgCheckModules=PkgCheckModules)
##
## SConsEnvironment.DistSources = DistSources
## SConsEnvironment.DistFiles = DistFiles
## env['DISTDIR'] = "${PACKAGE}-${VERSION}"
##
## #env.Command(env.Dir("$DISTDIR"), None, make_distdir)
##
## distdir_alias = env.Alias("distdir", None, make_distdir)
## dist_alias = env.Alias("dist", None, make_dist)
## env.Depends(dist_alias, distdir_alias)
## distcheck_alias = env.Alias("distcheck", None, make_distcheck)
## env.Depends(distcheck_alias, distdir_alias)
## env.AlwaysBuild(env.Alias('check'))
##
## #env['TARFLAGS'] ='-c -z'
## #env['TARSUFFIX'] = '.tar.gz'
## #tar = env.Tar('${PACKAGE}-${VERSION}.tar.gz', "${DISTDIR}")
## #env.Depends(tar, distdir_alias)
## #print env['DEFAULT_TARGETS']
##
## #env.Depends(distdir_alias, "${DISTFILES}")
## #env.Alias('dist', tar)
## env.AlwaysBuild('dist')
## env.AlwaysBuild('distdir')
## env.AlwaysBuild('distcheck')
## env.DistFiles(['SConstruct', 'scons/gnome.py'])
##
## env['BUILDERS']['EnvSubstFile'] = SCons.Builder.Builder(action=env_subst)
##
## SConsEnvironment.PythonByteCompile = env.Action(byte_compile_python)
##
## env.Install = new.instancemethod(InstallWithDestDir, env, env.__class__)
## env.InstallAs = new.instancemethod(InstallAsWithDestDir, env, env.__class__)
##
##
##