]> git.lyx.org Git - lyx.git/blob - development/scons/scons_utils.py
Scons: add support for system iconv function (from glibc)
[lyx.git] / development / scons / scons_utils.py
1 # vi:filetype=python:expandtab:tabstop=4:shiftwidth=4
2 #
3 # file scons_utils.py
4 #
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7 #
8 # \author Bo Peng
9 # Full author contact details are available in file CREDITS.
10 #
11 # This file defines all the utility functions for the
12 # scons-based build system of lyx
13 #
14
15 import os, sys, re, shutil, glob
16 from SCons.Util import WhereIs
17
18
19 def writeToFile(filename, lines, append = False):
20     " utility function: write or append lines to filename "
21     # create directory if needed
22     dir = os.path.split(filename)[0]
23     if dir != '' and not os.path.isdir(dir):
24         os.makedirs(dir)
25     if append:
26         file = open(filename, 'a')
27     else:
28         file = open(filename, 'w')
29     file.write(lines)
30     file.close()
31
32
33 def env_subst(target, source, env):
34     ''' subst variables in source by those in env, and output to target
35         source and target are scons File() objects
36
37         %key% (not key itself) is an indication of substitution
38     '''
39     assert len(target) == 1
40     assert len(source) == 1
41     target_file = file(str(target[0]), "w")
42     source_file = file(str(source[0]), "r")
43
44     contents = source_file.read()
45     for k, v in env.items():
46         try:
47             val = env.subst('$'+k)
48             # temporary fix for the \Resource backslash problem
49             val = val.replace('\\', '/')
50             # multi-line replacement
51             val = val.replace('\n',r'\\n\\\n')
52             contents = re.sub('@'+k+'@', val, contents)
53             contents = re.sub('%'+k+'%', val, contents)
54         except:
55             pass
56     target_file.write(contents + "\n")
57     target_file.close()
58     #st = os.stat(str(source[0]))
59     #os.chmod(str(target[0]), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
60
61 #
62 # autoconf tests
63 #
64
65 def checkPkgConfig(conf, version):
66     ''' Return false if pkg_config does not exist, or is too old '''
67     conf.Message('Checking for pkg-config...')
68     ret = conf.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
69     conf.Result(ret)
70     return ret
71
72
73 def checkPackage(conf, pkg):
74     ''' check if pkg is under the control of conf '''
75     conf.Message('Checking for package %s...' % pkg)
76     ret = conf.TryAction("pkg-config --print-errors --exists %s" % pkg)[0]
77     conf.Result(ret)
78     return ret
79
80
81 def checkMkdirOneArg(conf):
82     check_mkdir_one_arg_source = """
83 #include <sys/stat.h>
84 int main()
85 {
86     mkdir("somedir");
87 }
88 """
89     conf.Message('Checking for the number of args for mkdir... ')
90     ret = conf.TryLink(check_mkdir_one_arg_source, '.c') or \
91         conf.TryLink('#include <unistd.h>' + check_mkdir_one_arg_source, '.c') or \
92         conf.TryLink('#include <direct.h>' + check_mkdir_one_arg_source, '.c')
93     if ret:
94         conf.Result('one')
95     else:
96         conf.Result('two')
97     return ret
98
99
100 def checkCXXGlobalCstd(conf):
101     ''' Check the use of std::tolower or tolower '''
102     check_global_cstd_source = '''
103 #include <cctype>
104 using std::tolower;
105 int main()
106 {
107     return 0;
108 }
109 '''
110     conf.Message('Check for the use of global cstd... ')
111     ret = conf.TryLink(check_global_cstd_source, '.c')
112     conf.Result(ret)
113     return ret
114
115
116 def checkSelectArgType(conf):
117     ''' Adapted from autoconf '''
118     conf.Message('Checking for arg types for select... ')
119     for arg234 in ['fd_set *', 'int *', 'void *']:
120         for arg1 in ['int', 'size_t', 'unsigned long', 'unsigned']:
121             for arg5 in ['struct timeval *', 'const struct timeval *']:
122                 check_select_source = '''
123 #if HAVE_SYS_SELECT_H
124 # include <sys/select.h>
125 #endif
126 #if HAVE_SYS_SOCKET_H
127 # include <sys/socket.h>
128 #endif
129 extern int select (%s, %s, %s, %s, %s);
130 int main()
131 {
132     return(0);
133 }
134 ''' % (arg1, arg234, arg234, arg234, arg5)
135                 ret = conf.TryLink(check_select_source, '.c')
136                 if ret:
137                     conf.Result(ret)
138                     return (arg1, arg234, arg5)
139     conf.Result('no (use default)')
140     return ('int', 'int *', 'struct timeval *')
141
142
143 def checkBoostLibraries(conf, libs, lib_paths, inc_paths, version, isDebug):
144     ''' look for boost libraries
145       libs: library names
146       lib_paths: try these paths for boost libraries
147       inc_paths: try these paths for boost headers
148       version:   required boost version
149       isDebug:   if true, use debug libraries
150     '''
151     conf.Message('Checking for boost library %s... ' % ', '.join(libs))
152     found_lib = False
153     found_inc = False
154     lib_names = []
155     lib_path = None
156     inc_path = None
157     for path in lib_paths:
158         # direct form: e.g. libboost_iostreams.a
159         # ignore isDebug
160         if False not in [os.path.isfile(os.path.join(path, 'libboost_%s.a' % lib)) for lib in libs]:
161             conf.Result('yes')
162             found_lib = True
163             lib_path = path
164             lib_names = libs
165             break
166         for lib in libs:
167             # get all the libs, then filter for the right library
168             files = glob.glob(os.path.join(path, 'libboost_%s-*.a' % lib))
169             # check things like libboost_iostreams-gcc-mt-d-1_33_1.a
170             if len(files) > 0:
171                 # runtime code includes s,g,y,d,p,n, where we should look for
172                 # d,g,y for debug, s,p,n for release
173                 if isDebug:
174                     lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-[^spn]+-%s.a' % (lib, version), x), files)
175                 else:
176                     lib_files = filter(lambda x: re.search('libboost_%s-\w+-mt-([^dgy]+-)*%s.a' % (lib, version), x), files)
177                 if len(lib_files) == 0:
178                     print 'Warning: Can not find an appropriate boost library in %s.' % path
179                     lib_files = filter(lambda x: re.search('libboost_%s-[\w-]+%s.a' % (lib, version), x), files)
180                     if len(lib_files) > 0:
181                         print 'Use library %s' % lib_files[0]
182                 if len(lib_files) > 0:
183                     # get xxx-gcc-1_33_1 from /usr/local/lib/libboost_xxx-gcc-1_33_1.a
184                     lib_names.append(lib_files[0].split(os.sep)[-1][3:-2])
185         if len(lib_names) == len(libs):
186             found_lib = True
187             lib_path = path
188             break
189     if not found_lib:
190         conf.Result('no')
191         return (None, None, None)
192     # check version number in boost/version.hpp
193     def isValidBoostDir(dir):
194         file = os.path.join(dir, 'boost', 'version.hpp')
195         version_string = '#define BOOST_LIB_VERSION "%s"' % version
196         return os.path.isfile(file) and version_string in open(file).read()
197     # check for boost header file
198     for path in inc_paths:
199         if isValidBoostDir(path):
200             inc_path = path
201             found_inc = True
202         else:   # check path/boost_1_xx_x/boost
203             dirs = glob.glob(os.path.join(path, 'boost-*'))
204             if len(dirs) > 0 and isValidBoostDir(dirs[0]):
205                 inc_path = dirs[0]
206                 found_inc = True
207     # return result
208     if found_inc:
209         conf.Result('yes')
210         return (lib_names, lib_path, inc_path)
211     else:
212         conf.Result('no')
213         return (None, None, None)
214
215
216 def checkCommand(conf, cmd):
217     ''' check the existence of a command
218         return full path to the command, or none
219     '''
220     conf.Message('Checking for command %s...' % cmd)
221     res = WhereIs(cmd)
222     conf.Result(res is not None)
223     return res
224
225
226 def checkLC_MESSAGES(conf):
227     ''' check the definition of LC_MESSAGES '''
228     check_LC_MESSAGES = '''
229 #include <locale.h>
230 int main()
231 {
232     return LC_MESSAGES;
233 }
234 '''
235     conf.Message('Check for LC_MESSAGES in locale.h... ')
236     ret = conf.TryLink(check_LC_MESSAGES, '.c')
237     conf.Result(ret)
238     return ret
239
240
241 def checkIconvConst(conf):
242     ''' check the declaration of iconv '''
243     check_iconv_const = '''
244 #include <iconv.h>
245 // this declaration will fail when there already exists a non const char** 
246 // version which returns size_t
247 double iconv(iconv_t cd,  char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
248 int main() {
249     return 0; 
250 }
251 '''
252     conf.Message('Check if the declaration of iconv needs const... ')
253     ret = conf.TryLink(check_iconv_const, '.c')
254     conf.Result(ret)
255     return ret
256
257
258 def createConfigFile(conf, config_file,
259     config_pre = '', config_post = '',
260     headers = [], functions = [], types = [], libs = [],
261     custom_tests = [], extra_items = []):
262     ''' create a configuration file, with options
263         config_file: which file to create
264         config_pre: first part of the config file
265         config_post: last part of the config file
266         headers: header files to check, in the form of a list of
267             ('file', 'HAVE_FILE', 'c'/'c++')
268         functions: functions to check, in the form of a list of
269             ('func', 'HAVE_func', 'include lines'/None)
270         types: types to check, in the form of a list of
271             ('type', 'HAVE_TYPE', 'includelines'/None)
272         libs: libraries to check, in the form of a list of
273             ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
274             or any of the libs exists if 'lib' is a list of libs.
275             Optionally, user can provide another key LIB_NAME, that will
276             be set to the detected lib (or None otherwise).
277         custom_tests: extra tests to perform, in the form of a list of
278             (test (True/False), 'key', 'desc', 'true config line', 'false config line')
279             If the last two are ignored, '#define key 1' '/*#undef key */'
280             will be used.
281         extra_items: extra configuration lines, in the form of a list of
282             ('config', 'description')
283     Return:
284         The result of each test, as a dictioanry of
285             res['XXX'] = True/False
286         XXX are keys defined in each argument.
287     '''
288     cont = config_pre + '\n'
289     result = {}
290     # add to this string, in appropriate format
291     def configString(lines, desc=''):
292         text = ''
293         if lines.strip() != '':
294             if desc != '':
295                 text += '/* ' + desc + ' */\n'
296             text += lines + '\n\n'
297         return text
298     #
299     # headers
300     for header in headers:
301         description = "Define to 1 if you have the <%s> header file." % header[0]
302         if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
303             (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
304             result[header[1]] = True
305             cont += configString('#define %s 1' % header[1], desc = description)
306         else:
307             result[header[1]] = False
308             cont += configString('/* #undef %s */' % header[1], desc = description)
309     # functions
310     for func in functions:
311         description = "Define to 1 if you have the `%s' function." % func[0]
312         if conf.CheckFunc(func[0], header=func[2]):
313             result[func[1]] = True
314             cont += configString('#define %s 1' % func[1], desc = description)
315         else:
316             result[func[1]] = False
317             cont += configString('/* #undef %s */' % func[1], desc = description)
318     # types
319     for t in types:
320         description = "Define to 1 if you have the `%s' type." % t[0]
321         if conf.CheckType(t[0], includes=t[2]):
322             result[t[1]] = True
323             cont += configString('#define %s 1' % t[1], desc = description)
324         else:
325             result[t[1]] = False
326             cont += configString('/* #undef %s */' % t[1],  desc = description)
327     # libraries
328     for lib in libs:
329         description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
330         if type(lib[0]) is type(''):
331             lib_list = [lib[0]]
332         else:
333             lib_list = lib[0]
334         # check if any of the lib exists
335         result[lib[1]] = False
336         # if user want the name of the lib detected
337         if len(lib) == 3:
338             result[lib[2]] = None
339         for ll in lib_list:
340             if conf.CheckLib(ll):
341                 result[lib[1]] = True
342                 if len(lib) == 3:
343                     result[lib[2]] = ll
344                 cont += configString('#define %s 1' % lib[1], desc = description)
345                 break
346         # if not found
347         if not result[lib[1]]:
348             cont += configString('/* #undef %s */' % lib[1], desc = description)
349     # custom tests
350     for test in custom_tests:
351         if test[0]:
352             result[test[1]] = True
353             if len(test) == 3:
354                 cont += configString('#define %s 1' % test[1], desc = test[2])
355             else:
356                 cont += configString(test[3], desc = test[2])
357         else:
358             result[test[1]] = False
359             if len(test) == 3:
360                 cont += configString('/* #undef %s */' % test[1], desc = test[2])
361             else:
362                 cont += configString(test[4], desc = test[2])
363     # extra items (no key is returned)
364     for item in extra_items:
365         cont += configString(item[0], desc = item[1])
366     # add the last part
367     cont += '\n' + config_post + '\n'
368     # write to file
369     writeToFile(config_file, cont)
370     return result
371
372
373 def installCygwinLDScript(path):
374     ''' Install i386pe.x-no-rdata '''
375     ld_script = os.path.join(path, 'i386pe.x-no-rdata')
376     script = open(ld_script, 'w')
377     script.write('''/* specific linker script avoiding .rdata sections, for normal executables
378 for a reference see
379 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
380 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
381 */
382 OUTPUT_FORMAT(pei-i386)
383 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
384 ENTRY(_mainCRTStartup)
385 SECTIONS
386 {
387   .text  __image_base__ + __section_alignment__  :
388   {
389     *(.init)
390     *(.text)
391     *(SORT(.text$*))
392     *(.glue_7t)
393     *(.glue_7)
394     ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
395                         LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*));  LONG (0);
396     ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
397                         LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*));  LONG (0);
398     *(.fini)
399     /* ??? Why is .gcc_exc here?  */
400     *(.gcc_exc)
401     PROVIDE (etext = .);
402     *(.gcc_except_table)
403   }
404   /* The Cygwin32 library uses a section to avoid copying certain data
405     on fork.  This used to be named ".data".  The linker used
406     to include this between __data_start__ and __data_end__, but that
407     breaks building the cygwin32 dll.  Instead, we name the section
408     ".data_cygwin_nocopy" and explictly include it after __data_end__. */
409   .data BLOCK(__section_alignment__) :
410   {
411     __data_start__ = . ;
412     *(.data)
413     *(.data2)
414     *(SORT(.data$*))
415     *(.rdata)
416     *(SORT(.rdata$*))
417     *(.eh_frame)
418     ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
419     __RUNTIME_PSEUDO_RELOC_LIST__ = .;
420     *(.rdata_runtime_pseudo_reloc)
421     ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
422     __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
423     __data_end__ = . ;
424     *(.data_cygwin_nocopy)
425   }
426   .rdata BLOCK(__section_alignment__) :
427   {
428   }
429   .pdata BLOCK(__section_alignment__) :
430   {
431     *(.pdata)
432   }
433   .bss BLOCK(__section_alignment__) :
434   {
435     __bss_start__ = . ;
436     *(.bss)
437     *(COMMON)
438     __bss_end__ = . ;
439   }
440   .edata BLOCK(__section_alignment__) :
441   {
442     *(.edata)
443   }
444   /DISCARD/ :
445   {
446     *(.debug$S)
447     *(.debug$T)
448     *(.debug$F)
449     *(.drectve)
450   }
451   .idata BLOCK(__section_alignment__) :
452   {
453     /* This cannot currently be handled with grouped sections.
454         See pe.em:sort_sections.  */
455     SORT(*)(.idata$2)
456     SORT(*)(.idata$3)
457     /* These zeroes mark the end of the import list.  */
458     LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
459     SORT(*)(.idata$4)
460     SORT(*)(.idata$5)
461     SORT(*)(.idata$6)
462     SORT(*)(.idata$7)
463   }
464   .CRT BLOCK(__section_alignment__) :
465   {
466     ___crt_xc_start__ = . ;
467     *(SORT(.CRT$XC*))  /* C initialization */
468     ___crt_xc_end__ = . ;
469     ___crt_xi_start__ = . ;
470     *(SORT(.CRT$XI*))  /* C++ initialization */
471     ___crt_xi_end__ = . ;
472     ___crt_xl_start__ = . ;
473     *(SORT(.CRT$XL*))  /* TLS callbacks */
474     /* ___crt_xl_end__ is defined in the TLS Directory support code */
475     ___crt_xp_start__ = . ;
476     *(SORT(.CRT$XP*))  /* Pre-termination */
477     ___crt_xp_end__ = . ;
478     ___crt_xt_start__ = . ;
479     *(SORT(.CRT$XT*))  /* Termination */
480     ___crt_xt_end__ = . ;
481   }
482   .tls BLOCK(__section_alignment__) :
483   {
484     ___tls_start__ = . ;
485     *(.tls)
486     *(.tls$)
487     *(SORT(.tls$*))
488     ___tls_end__ = . ;
489   }
490   .endjunk BLOCK(__section_alignment__) :
491   {
492     /* end is deprecated, don't use it */
493     PROVIDE (end = .);
494     PROVIDE ( _end = .);
495     __end__ = .;
496   }
497   .rsrc BLOCK(__section_alignment__) :
498   {
499     *(.rsrc)
500     *(SORT(.rsrc$*))
501   }
502   .reloc BLOCK(__section_alignment__) :
503   {
504     *(.reloc)
505   }
506   .stab BLOCK(__section_alignment__) (NOLOAD) :
507   {
508     *(.stab)
509   }
510   .stabstr BLOCK(__section_alignment__) (NOLOAD) :
511   {
512     *(.stabstr)
513   }
514   /* DWARF debug sections.
515     Symbols in the DWARF debugging sections are relative to the beginning
516     of the section.  Unlike other targets that fake this by putting the
517     section VMA at 0, the PE format will not allow it.  */
518   /* DWARF 1.1 and DWARF 2.  */
519   .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
520   {
521     *(.debug_aranges)
522   }
523   .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
524   {
525     *(.debug_pubnames)
526   }
527   /* DWARF 2.  */
528   .debug_info BLOCK(__section_alignment__) (NOLOAD) :
529   {
530     *(.debug_info) *(.gnu.linkonce.wi.*)
531   }
532   .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
533   {
534     *(.debug_abbrev)
535   }
536   .debug_line BLOCK(__section_alignment__) (NOLOAD) :
537   {
538     *(.debug_line)
539   }
540   .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
541   {
542     *(.debug_frame)
543   }
544   .debug_str BLOCK(__section_alignment__) (NOLOAD) :
545   {
546     *(.debug_str)
547   }
548   .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
549   {
550     *(.debug_loc)
551   }
552   .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
553   {
554     *(.debug_macinfo)
555   }
556   /* SGI/MIPS DWARF 2 extensions.  */
557   .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
558   {
559     *(.debug_weaknames)
560   }
561   .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
562   {
563     *(.debug_funcnames)
564   }
565   .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
566   {
567     *(.debug_typenames)
568   }
569   .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
570   {
571     *(.debug_varnames)
572   }
573   /* DWARF 3.  */
574   .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
575   {
576     *(.debug_ranges)
577   }
578 }
579 ''')
580     script.close()
581     return(ld_script)
582
583
584 try:
585     # these will be used under win32
586     import win32file
587     import win32event
588     import win32process
589     import win32security
590 except:
591     # does not matter if it fails on other systems
592     pass
593
594
595 class loggedSpawn:
596     def __init__(self, env, logfile, longarg, info):
597         # save the spawn system
598         self.env = env
599         self.logfile = logfile
600         # clear the logfile (it may not exist)
601         if logfile != '':
602             # this will overwrite existing content.
603             writeToFile(logfile, info, append=False)
604         #
605         self.longarg = longarg
606         # get hold of the old spawn? (necessary?)
607         self._spawn = env['SPAWN']
608
609     # define new SPAWN
610     def spawn(self, sh, escape, cmd, args, spawnenv):
611         # get command line
612         newargs = ' '.join(map(escape, args[1:]))
613         cmdline = cmd + " " + newargs
614         #
615         # if log is not empty, write to it
616         if self.logfile != '':
617             # this tend to be slow (?) but ensure correct output
618             # Note that cmdline may be long so I do not escape it
619             try:
620                 # since this is not an essential operation, proceed if things go wrong here.
621                 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
622             except:
623                 print "Warning: can not write to log file ", self.logfile
624         #
625         # if the command is not too long, use the old
626         if not self.longarg or len(cmdline) < 8000:
627             exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
628         else:
629             sAttrs = win32security.SECURITY_ATTRIBUTES()
630             StartupInfo = win32process.STARTUPINFO()
631             for var in spawnenv:
632                 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
633             # check for any special operating system commands
634             if cmd == 'del':
635                 for arg in args[1:]:
636                     win32file.DeleteFile(arg)
637                 exit_code = 0
638             else:
639                 # otherwise execute the command.
640                 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
641                 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
642                 exit_code = win32process.GetExitCodeProcess(hProcess)
643                 win32file.CloseHandle(hProcess);
644                 win32file.CloseHandle(hThread);
645         return exit_code
646
647
648 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
649     ''' This function modify env and allow logging of
650         commands to a logfile. If the argument is too long
651         a win32 spawn will be used instead of the system one
652     '''
653     #
654     # create a new spwn object
655     ls = loggedSpawn(env, logfile, longarg, info)
656     # replace the old SPAWN by the new function
657     env['SPAWN'] = ls.spawn
658