]> git.lyx.org Git - lyx.git/blobdiff - development/scons/scons_utils.py
fix scons for Andre's LYX_DATE changes
[lyx.git] / development / scons / scons_utils.py
index 25e355e95d3835951685e504149143d7f129d9e7..9fa18265c49a960c712b0640f2138ebef96d9efd 100644 (file)
 #
 
 import os, sys, re, shutil, glob
-from SCons.Util import WhereIs
+from SCons.Util import *
+
+
+def getVerFromConfigure(path):
+    ''' get lyx version from the AC_INIT line of configure.ac,
+        and LYX_DATE from an AC_SUBST line.
+    '''
+    try:
+        config = open(os.path.join(path, 'configure.ac'))
+    except:
+        print "Can not open configure.ac. "
+        return 'x.x.x'
+    # find a line like follows
+    # AC_INIT(LyX,1.4.4svn,[lyx-devel@lists.lyx.org],[lyx])
+    ver_pat = re.compile('AC_INIT\([^,]+,([^,]+),')
+    date_pat = re.compile('AC_SUBST\(LYX_DATE, \["(.*)"\]\)')
+    version = 'x.x.x'
+    date = 'Not released'
+    for line in config.readlines():
+        if ver_pat.match(line):
+            (version,) = ver_pat.match(line).groups()
+        if date_pat.match(line):
+            (date,) = date_pat.match(line).groups()
+        if version != 'x.x.x' and date != 'Not released':
+            break
+    return version.strip(), date.strip()
+
+
+def relativePath(path, base):
+    '''return relative path from base, which is usually top source dir'''
+    # full pathname of path
+    path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
+    path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
+    if path1[:len(path2)] != path2:
+        print "Path %s is not under top source directory" % path
+    if len(path2) == len(path1):
+        return ''
+    path3 = os.path.join(*path1[len(path2):]);
+    # replace all \ by / such that we get the same comments on Windows and *nix
+    path3 = path3.replace('\\', '/')
+    return path3
+
+
+def isSubDir(path, base):
+    '''Whether or not path is a subdirectory of base'''
+    path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
+    path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
+    return len(path2) <= len(path1) and path1[:len(path2)] == path2
 
 
 def writeToFile(filename, lines, append = False):
@@ -50,7 +97,6 @@ def env_subst(target, source, env):
             # 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")
@@ -59,17 +105,86 @@ def env_subst(target, source, env):
     #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
 
 
-#
-# glob filenames
-#
-def globSource(dir, pattern, build_dir = None, exclude = [], include = []):
-    ''' glob files, in dir and use build_dir as returned path name '''
-    # exclude 'exclude+include' to avoid duplicate items in files
-    files = include + filter(lambda x: x not in exclude + include, glob.glob1(dir, pattern))
-    if build_dir is None:
-        return files
+def env_nsis(source, target, env, for_signature):
+    ''' Get nsis command line '''
+    def quoteIfSpaced(str):
+        if ' ' in str:
+            return '"' + str + '"'
+        else:
+            return str
+    ret = env['NSIS'] + " /V1 "
+    if env.has_key('NSISFLAGS'):
+        for flag in env['NSISFLAGS']:
+            ret += flag
+            ret += ' '
+    if env.has_key('NSISDEFINES'):
+        for d in env['NSISDEFINES']:
+            ret += '/D'+d
+            if env['NSISDEFINES'][d]:
+                ret += '=' + quoteIfSpaced(env['NSISDEFINES'][d])
+            ret += ' '
+    # bundled?
+    if '-bundle.exe' in str(target[0]):
+        ret += '/DSETUPTYPE_BUNDLE=1 '
+    for s in source:
+        ret += quoteIfSpaced(str(s))
+    return ret
+
+
+def env_toc(target, source, env):
+    '''Generate target from source files'''
+    # this is very tricky because we need to use installed lyx2lyx with 
+    # correct lyx2lyx_version.py
+    sys.path.append(env['LYX2LYX_DEST'])
+    sys.path.append(env.Dir('$TOP_SRCDIR/lib/doc').abspath)
+    import doc_toc
+    # build toc
+    doc_toc.build_toc(str(target[0]), [file.abspath for file in source])
+    
+    
+def env_cat(target, source, env):
+    '''Cat source > target. Avoid pipe to increase portability'''
+    output = open(env.File(target[0]).abspath, 'w')
+    for src in source:
+        input = open(env.File(src).abspath)
+        output.write(input.read())
+        input.close()
+    output.close()
+
+
+def env_potfiles(target, source, env):
+    '''Build po/POTFILES.in'''
+    # command 
+    #   grep -l '_(\".*\")' `find src \( -name '*.h' -o -name '*.cpp' -o -name '*.cpp.in' \) -print` | grep -v -e "src/support/Package.cpp$$" | sort | uniq
+    # is used under *nix but windows users have to do these all in python
+    target_file = open(str(target[0]), "w")
+    potfiles = []
+    trans = re.compile('_\(".*"\)', re.M)
+    for file in source:
+        rel_file = relativePath(str(file), env.subst('$TOP_SRCDIR'))
+        if rel_file not in potfiles and trans.search(open(str(file)).read()):
+            potfiles.append(rel_file)
+    potfiles.sort()
+    print >> target_file, '\n'.join(potfiles)
+    target_file.close()
+
+    
+def createResFromIcon(env, icon_file, rc_file):
+    ''' create a rc file with icon, and return res file (windows only) '''
+    if os.name == 'nt':
+        rc_name = env.File(rc_file).abspath
+        dir = os.path.split(rc_name)[0]
+        if not os.path.isdir(dir):
+            os.makedirs(dir)
+        rc = open(rc_name, 'w')
+        print >> rc, 'IDI_ICON1  ICON DISCARDABLE "%s"' % \
+            os.path.join(env.Dir('$TOP_SRCDIR').abspath, 'development', 'win32',
+                'packaging', 'icons', icon_file).replace('\\', '\\\\')
+        rc.close()
+        return env.RES(rc_name)
     else:
-        return ['%s/%s' % (build_dir, x) for x in files]
+        return []
+
 
 #
 # autoconf tests
@@ -111,7 +226,7 @@ int main()
 
 
 def checkCXXGlobalCstd(conf):
-    ''' Check the use of std::tolower or tolower '''
+    ''' Checking the use of std::tolower or tolower '''
     check_global_cstd_source = '''
 #include <cctype>
 using std::tolower;
@@ -120,7 +235,7 @@ int main()
     return 0;
 }
 '''
-    conf.Message('Check for the use of global cstd... ')
+    conf.Message('Checking for the use of global cstd... ')
     ret = conf.TryLink(check_global_cstd_source, '.c')
     conf.Result(ret)
     return ret
@@ -153,73 +268,86 @@ int main()
     return ('int', 'int *', 'struct timeval *')
 
 
-def checkBoostLibraries(conf, libs, lib_paths, inc_paths, version, isDebug):
+def checkBoostLibraries(conf, libs, lib_paths, inc_paths, versions, isDebug):
     ''' look for boost libraries
       libs: library names
       lib_paths: try these paths for boost libraries
       inc_paths: try these paths for boost headers
-      version:   required boost version
+      versions:   supported boost versions
       isDebug:   if true, use debug libraries
     '''
     conf.Message('Checking for boost library %s... ' % ', '.join(libs))
+    libprefix = conf.env['LIBPREFIX']
+    libsuffix = '(%s|%s)' % (conf.env['LIBSUFFIX'], conf.env['SHLIBSUFFIX'])
     found_lib = False
     found_inc = False
     lib_names = []
     lib_path = None
     inc_path = None
     for path in lib_paths:
-        # direct form: e.g. libboost_iostreams.a
-        # ignore isDebug
-        if False not in [os.path.isfile(os.path.join(path, 'libboost_%s.a' % lib)) for lib in libs]:
-            conf.Result('yes')
-            found_lib = True
-            lib_path = path
-            lib_names = libs
-            break
+        conf.Log("Looking into %s\n" % path)
         for lib in libs:
             # get all the libs, then filter for the right library
-            files = glob.glob(os.path.join(path, 'libboost_%s-*.a' % lib))
+            files = glob.glob(os.path.join(path, '%sboost_%s-*.*' % (libprefix, lib)))
             # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
             if len(files) > 0:
+                conf.Log("Find boost libraries: %s\n" % files)
                 # runtime code includes s,g,y,d,p,n, where we should look for
                 # d,g,y for debug, s,p,n for release
+                lib_files = []
                 if isDebug:
-                    lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-[^spn]+-%s.a' % (lib, version), x), files)
+                    for ver in versions:
+                        lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-[^spn]+-%s%s' % (libprefix, lib, ver, libsuffix), x), files)
                 else:
-                    lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-([^dgy]+-)*%s.a' % (lib, version), x), files)
+                    for ver in versions:
+                        lib_files += filter(lambda x: re.search('%sboost_%s-\w+-mt-([^dgy]+-)*%s%s' % (libprefix, lib, ver, libsuffix), x), files)
                 if len(lib_files) == 0:
-                    print 'Warning: Can not find an appropriate boost library in %s.' % path
-                    lib_files = filter(lambda x: re.search('libboost_%s-[\w-]+%s.a' % (lib, version), x), files)
-                    if len(lib_files) > 0:
-                        print 'Use library ' % lib_files[0]
+                    # use alternative libraries
+                    for ver in versions:
+                        lib_files += filter(lambda x: re.search('%sboost_%s-[\w-]+%s%s' % (libprefix, lib, ver, libsuffix), x), files)
                 if len(lib_files) > 0:
                     # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
-                    lib_names.append(lib_files[0].split(os.sep)[-1][3:-2])
+                    name = lib_files[0].split(os.sep)[-1][len(libprefix):]
+                    lib_names.append(name.split('.')[0])
+                    conf.Log("Qualified libraries: %s\n" % lib_names)
+                else:
+                    conf.Log("No qualified library is found.\n")
+                    break
         if len(lib_names) == len(libs):
             found_lib = True
             lib_path = path
             break
     if not found_lib:
+        if len(lib_names) == 0:
+            conf.Log("No boost library is found\n")
+        else:
+            conf.Log("Found boost libraries: %s\n" % lib_names)
         conf.Result('no')
         return (None, None, None)
     # check version number in boost/version.hpp
     def isValidBoostDir(dir):
-        file = os.path.join(dir, 'boost', 'version.hpp')
-        version_string = '#define BOOST_LIB_VERSION "%s"' % version
-        return os.path.isfile(file) and version_string in open(file).read()
+        version_file = os.path.join(dir, 'boost', 'version.hpp')
+        if not os.path.isfile(version_file):
+            return False
+        version_file_content = open(version_file).read()
+        version_strings = ['#define BOOST_LIB_VERSION "%s"' % ver for ver in versions]
+        return True in [x in version_file_content for x in version_strings]
     # check for boost header file
     for path in inc_paths:
+        conf.Log("Checking for inc path: %s\n" % path)
         if isValidBoostDir(path):
             inc_path = path
             found_inc = True
         else:   # check path/boost_1_xx_x/boost
             dirs = glob.glob(os.path.join(path, 'boost-*'))
             if len(dirs) > 0 and isValidBoostDir(dirs[0]):
+                conf.Log("Checing for sub directory: %s\n" % dirs[0])
                 inc_path = dirs[0]
                 found_inc = True
     # return result
     if found_inc:
         conf.Result('yes')
+        conf.Log('Using boost libraries %s\n' % (', '.join(lib_names)))
         return (lib_names, lib_path, inc_path)
     else:
         conf.Result('no')
@@ -236,6 +364,30 @@ def checkCommand(conf, cmd):
     return res
 
 
+def checkNSIS(conf):
+    ''' check the existence of nsis compiler, return the fullpath '''
+    conf.Message('Checking for nsis compiler...')
+    res = None
+    if can_read_reg:
+        # If we can read the registry, get the NSIS command from it
+        try:
+            k = RegOpenKeyEx(hkey_mod.HKEY_LOCAL_MACHINE,
+                                  'SOFTWARE\\NSIS')
+            val, tok = RegQueryValueEx(k,None)
+            ret = val + os.path.sep + 'makensis.exe'
+            if os.path.isfile(ret):
+                res = '"' + ret + '"'
+            else:
+                res = None
+        except:
+            pass # Couldn't find the key, just act like we can't read the registry
+    # Hope it's on the path
+    if res is None:
+        res = WhereIs('makensis.exe')
+    conf.Result(res is not None)
+    return res
+
+
 def checkLC_MESSAGES(conf):
     ''' check the definition of LC_MESSAGES '''
     check_LC_MESSAGES = '''
@@ -245,7 +397,7 @@ int main()
     return LC_MESSAGES;
 }
 '''
-    conf.Message('Check for LC_MESSAGES in locale.h... ')
+    conf.Message('Checking for LC_MESSAGES in locale.h... ')
     ret = conf.TryLink(check_LC_MESSAGES, '.c')
     conf.Result(ret)
     return ret
@@ -254,34 +406,60 @@ int main()
 def checkIconvConst(conf):
     ''' check the declaration of iconv '''
     check_iconv_const = '''
-#include <stdlib.h>
 #include <iconv.h>
-extern
-#ifdef __cplusplus
-"C"
-#endif
-#if defined(__STDC__) || defined(__cplusplus)
-#ifndef LIBICONV_DLL_EXPORTED
-size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
-#endif
-#else
-size_t iconv();
-#endif
+// this declaration will fail when there already exists a non const char** 
+// version which returns size_t
+double iconv(iconv_t cd,  char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
+int main() {
+    return 0; 
+}
+'''
+    conf.Message('Checking if the declaration of iconv needs const... ')
+    ret = conf.TryLink(check_iconv_const, '.c')
+    conf.Result(ret)
+    return ret
 
+
+def checkSizeOfWChar(conf):
+    ''' check the size of wchar '''
+    check_sizeof_wchar = '''
+int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
 int main()
 {
-    return 1;
+    return 0;
 }
 '''
-    conf.Message('Check if the declaration of iconv needs const... ')
-    ret = conf.TryLink(check_iconv_const, '.c')
-    conf.Result(ret)
+    conf.Message('Checking the size of wchar_t... ')
+    if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
+        ret = 2
+    elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
+        ret = 4
+    else:
+        ret = 0
+    conf.Result(str(ret))
     return ret
 
 
+def checkDeclaration(conf, func, headers):
+    ''' check if a function is declared in given headers '''
+    check_decl = '''
+#include <%%s>
+int main()
+{
+#ifndef %s
+    char *p = (char *) %s;
+#endif
+}
+''' % (func, func)
+    conf.Message('Checking for the declaration of function %s... ' % func)
+    ret = True in [conf.TryLink(check_decl % header, '.c') for header in headers]
+    conf.Result(ret)
+    return ret
+
+    
 def createConfigFile(conf, config_file,
     config_pre = '', config_post = '',
-    headers = [], functions = [], types = [], libs = [],
+    headers = [], functions = [], declarations = [], types = [], libs = [],
     custom_tests = [], extra_items = []):
     ''' create a configuration file, with options
         config_file: which file to create
@@ -291,6 +469,8 @@ def createConfigFile(conf, config_file,
             ('file', 'HAVE_FILE', 'c'/'c++')
         functions: functions to check, in the form of a list of
             ('func', 'HAVE_func', 'include lines'/None)
+        declarations: function declarations to check, in the form of a list of
+            ('func', 'HAVE_DECL_func', header_files)
         types: types to check, in the form of a list of
             ('type', 'HAVE_TYPE', 'includelines'/None)
         libs: libraries to check, in the form of a list of
@@ -325,28 +505,36 @@ def createConfigFile(conf, config_file,
         description = "Define to 1 if you have the <%s> header file." % header[0]
         if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
             (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
-            result[header[1]] = True
+            result[header[1]] = 1
             cont += configString('#define %s 1' % header[1], desc = description)
         else:
-            result[header[1]] = False
+            result[header[1]] = 0
             cont += configString('/* #undef %s */' % header[1], desc = description)
     # functions
     for func in functions:
         description = "Define to 1 if you have the `%s' function." % func[0]
         if conf.CheckFunc(func[0], header=func[2]):
-            result[func[1]] = True
+            result[func[1]] = 1
             cont += configString('#define %s 1' % func[1], desc = description)
         else:
-            result[func[1]] = False
+            result[func[1]] = 0
             cont += configString('/* #undef %s */' % func[1], desc = description)
+    for decl in declarations:
+        description = "Define to 1 if you have the declaration of `%s', and to 0 if you don't." % decl[0]
+        if conf.CheckDeclaration(decl[0], decl[2]):
+            result[decl[1]] = 1
+            cont += configString('#define %s 1' % decl[1], desc = description)
+        else:
+            result[decl[1]] = 0
+            cont += configString('/* #undef %s */' % decl[1], desc = description)
     # types
     for t in types:
         description = "Define to 1 if you have the `%s' type." % t[0]
         if conf.CheckType(t[0], includes=t[2]):
-            result[t[1]] = True
+            result[t[1]] = 1
             cont += configString('#define %s 1' % t[1], desc = description)
         else:
-            result[t[1]] = False
+            result[t[1]] = 0
             cont += configString('/* #undef %s */' % t[1],  desc = description)
     # libraries
     for lib in libs:
@@ -356,13 +544,13 @@ def createConfigFile(conf, config_file,
         else:
             lib_list = lib[0]
         # check if any of the lib exists
-        result[lib[1]] = False
+        result[lib[1]] = 0
         # if user want the name of the lib detected
         if len(lib) == 3:
             result[lib[2]] = None
         for ll in lib_list:
             if conf.CheckLib(ll):
-                result[lib[1]] = True
+                result[lib[1]] = 1
                 if len(lib) == 3:
                     result[lib[2]] = ll
                 cont += configString('#define %s 1' % lib[1], desc = description)
@@ -373,13 +561,13 @@ def createConfigFile(conf, config_file,
     # custom tests
     for test in custom_tests:
         if test[0]:
-            result[test[1]] = True
+            result[test[1]] = 1
             if len(test) == 3:
                 cont += configString('#define %s 1' % test[1], desc = test[2])
             else:
                 cont += configString(test[3], desc = test[2])
         else:
-            result[test[1]] = False
+            result[test[1]] = 0
             if len(test) == 3:
                 cont += configString('/* #undef %s */' % test[1], desc = test[2])
             else:
@@ -605,6 +793,27 @@ SECTIONS
     return(ld_script)
 
 
+def installCygwinPostinstallScript(path):
+    ''' Install lyx.sh '''
+    postinstall_script = os.path.join(path, 'lyx.sh')
+    script = open(postinstall_script, 'w')
+    script.write(r'''#!/bin/sh
+
+# Add /usr/share/lyx/fonts to /etc/fonts/local.conf
+# if it is not already there.
+if [ -f /etc/fonts/local.conf ]; then
+    grep -q /usr/share/lyx/fonts /etc/fonts/local.conf
+    if [ $? -ne 0 ]; then
+        sed 's/^<\/fontconfig>/<dir>\/usr\/share\/lyx\/fonts<\/dir>\n<\/fontconfig>/' /etc/fonts/local.conf > /etc/fonts/local.conf.tmp
+        mv -f /etc/fonts/local.conf.tmp /etc/fonts/local.conf
+        fc-cache /usr/share/lyx/fonts
+    fi
+fi
+    ''')
+    script.close()
+    return(postinstall_script)
+
+
 try:
     # these will be used under win32
     import win32file