]> git.lyx.org Git - lyx.git/blob - development/scons/scons_utils.py
Add phantom icons from Uwe and me (last part of bug 1473)
[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 checkSizeOfWChar(conf):
259     ''' check the size of wchar '''
260     check_sizeof_wchar = '''
261 int i[ ( sizeof(wchar_t)==%d ? 1 : -1 ) ];
262 int main()
263 {
264     return 0;
265 }
266 '''
267     conf.Message('Check the size of wchar_t... ')
268     if conf.TryLink(check_sizeof_wchar % 2, '.cpp'):
269         ret = 2
270     elif conf.TryLink(check_sizeof_wchar % 4, '.cpp'):
271         ret = 4
272     else:
273         ret = 0
274     conf.Result(str(ret))
275     return ret
276
277
278 def createConfigFile(conf, config_file,
279     config_pre = '', config_post = '',
280     headers = [], functions = [], types = [], libs = [],
281     custom_tests = [], extra_items = []):
282     ''' create a configuration file, with options
283         config_file: which file to create
284         config_pre: first part of the config file
285         config_post: last part of the config file
286         headers: header files to check, in the form of a list of
287             ('file', 'HAVE_FILE', 'c'/'c++')
288         functions: functions to check, in the form of a list of
289             ('func', 'HAVE_func', 'include lines'/None)
290         types: types to check, in the form of a list of
291             ('type', 'HAVE_TYPE', 'includelines'/None)
292         libs: libraries to check, in the form of a list of
293             ('lib', 'HAVE_LIB', 'LIB_NAME'). HAVE_LIB will be set if 'lib' exists,
294             or any of the libs exists if 'lib' is a list of libs.
295             Optionally, user can provide another key LIB_NAME, that will
296             be set to the detected lib (or None otherwise).
297         custom_tests: extra tests to perform, in the form of a list of
298             (test (True/False), 'key', 'desc', 'true config line', 'false config line')
299             If the last two are ignored, '#define key 1' '/*#undef key */'
300             will be used.
301         extra_items: extra configuration lines, in the form of a list of
302             ('config', 'description')
303     Return:
304         The result of each test, as a dictioanry of
305             res['XXX'] = True/False
306         XXX are keys defined in each argument.
307     '''
308     cont = config_pre + '\n'
309     result = {}
310     # add to this string, in appropriate format
311     def configString(lines, desc=''):
312         text = ''
313         if lines.strip() != '':
314             if desc != '':
315                 text += '/* ' + desc + ' */\n'
316             text += lines + '\n\n'
317         return text
318     #
319     # headers
320     for header in headers:
321         description = "Define to 1 if you have the <%s> header file." % header[0]
322         if (header[2] == 'c' and conf.CheckCHeader(header[0])) or \
323             (header[2] == 'cxx' and conf.CheckCXXHeader(header[0])):
324             result[header[1]] = True
325             cont += configString('#define %s 1' % header[1], desc = description)
326         else:
327             result[header[1]] = False
328             cont += configString('/* #undef %s */' % header[1], desc = description)
329     # functions
330     for func in functions:
331         description = "Define to 1 if you have the `%s' function." % func[0]
332         if conf.CheckFunc(func[0], header=func[2]):
333             result[func[1]] = True
334             cont += configString('#define %s 1' % func[1], desc = description)
335         else:
336             result[func[1]] = False
337             cont += configString('/* #undef %s */' % func[1], desc = description)
338     # types
339     for t in types:
340         description = "Define to 1 if you have the `%s' type." % t[0]
341         if conf.CheckType(t[0], includes=t[2]):
342             result[t[1]] = True
343             cont += configString('#define %s 1' % t[1], desc = description)
344         else:
345             result[t[1]] = False
346             cont += configString('/* #undef %s */' % t[1],  desc = description)
347     # libraries
348     for lib in libs:
349         description = "Define to 1 if you have the `%s' library (-l%s)." % (lib[0], lib[0])
350         if type(lib[0]) is type(''):
351             lib_list = [lib[0]]
352         else:
353             lib_list = lib[0]
354         # check if any of the lib exists
355         result[lib[1]] = False
356         # if user want the name of the lib detected
357         if len(lib) == 3:
358             result[lib[2]] = None
359         for ll in lib_list:
360             if conf.CheckLib(ll):
361                 result[lib[1]] = True
362                 if len(lib) == 3:
363                     result[lib[2]] = ll
364                 cont += configString('#define %s 1' % lib[1], desc = description)
365                 break
366         # if not found
367         if not result[lib[1]]:
368             cont += configString('/* #undef %s */' % lib[1], desc = description)
369     # custom tests
370     for test in custom_tests:
371         if test[0]:
372             result[test[1]] = True
373             if len(test) == 3:
374                 cont += configString('#define %s 1' % test[1], desc = test[2])
375             else:
376                 cont += configString(test[3], desc = test[2])
377         else:
378             result[test[1]] = False
379             if len(test) == 3:
380                 cont += configString('/* #undef %s */' % test[1], desc = test[2])
381             else:
382                 cont += configString(test[4], desc = test[2])
383     # extra items (no key is returned)
384     for item in extra_items:
385         cont += configString(item[0], desc = item[1])
386     # add the last part
387     cont += '\n' + config_post + '\n'
388     # write to file
389     writeToFile(config_file, cont)
390     return result
391
392
393 def installCygwinLDScript(path):
394     ''' Install i386pe.x-no-rdata '''
395     ld_script = os.path.join(path, 'i386pe.x-no-rdata')
396     script = open(ld_script, 'w')
397     script.write('''/* specific linker script avoiding .rdata sections, for normal executables
398 for a reference see
399 http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html
400 http://www.cygwin.com/ml/cygwin-apps/2004-09/msg00309.html
401 */
402 OUTPUT_FORMAT(pei-i386)
403 SEARCH_DIR("/usr/i686-pc-cygwin/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
404 ENTRY(_mainCRTStartup)
405 SECTIONS
406 {
407   .text  __image_base__ + __section_alignment__  :
408   {
409     *(.init)
410     *(.text)
411     *(SORT(.text$*))
412     *(.glue_7t)
413     *(.glue_7)
414     ___CTOR_LIST__ = .; __CTOR_LIST__ = . ;
415                         LONG (-1);*(.ctors); *(.ctor); *(SORT(.ctors.*));  LONG (0);
416     ___DTOR_LIST__ = .; __DTOR_LIST__ = . ;
417                         LONG (-1); *(.dtors); *(.dtor); *(SORT(.dtors.*));  LONG (0);
418     *(.fini)
419     /* ??? Why is .gcc_exc here?  */
420     *(.gcc_exc)
421     PROVIDE (etext = .);
422     *(.gcc_except_table)
423   }
424   /* The Cygwin32 library uses a section to avoid copying certain data
425     on fork.  This used to be named ".data".  The linker used
426     to include this between __data_start__ and __data_end__, but that
427     breaks building the cygwin32 dll.  Instead, we name the section
428     ".data_cygwin_nocopy" and explictly include it after __data_end__. */
429   .data BLOCK(__section_alignment__) :
430   {
431     __data_start__ = . ;
432     *(.data)
433     *(.data2)
434     *(SORT(.data$*))
435     *(.rdata)
436     *(SORT(.rdata$*))
437     *(.eh_frame)
438     ___RUNTIME_PSEUDO_RELOC_LIST__ = .;
439     __RUNTIME_PSEUDO_RELOC_LIST__ = .;
440     *(.rdata_runtime_pseudo_reloc)
441     ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
442     __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
443     __data_end__ = . ;
444     *(.data_cygwin_nocopy)
445   }
446   .rdata BLOCK(__section_alignment__) :
447   {
448   }
449   .pdata BLOCK(__section_alignment__) :
450   {
451     *(.pdata)
452   }
453   .bss BLOCK(__section_alignment__) :
454   {
455     __bss_start__ = . ;
456     *(.bss)
457     *(COMMON)
458     __bss_end__ = . ;
459   }
460   .edata BLOCK(__section_alignment__) :
461   {
462     *(.edata)
463   }
464   /DISCARD/ :
465   {
466     *(.debug$S)
467     *(.debug$T)
468     *(.debug$F)
469     *(.drectve)
470   }
471   .idata BLOCK(__section_alignment__) :
472   {
473     /* This cannot currently be handled with grouped sections.
474         See pe.em:sort_sections.  */
475     SORT(*)(.idata$2)
476     SORT(*)(.idata$3)
477     /* These zeroes mark the end of the import list.  */
478     LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
479     SORT(*)(.idata$4)
480     SORT(*)(.idata$5)
481     SORT(*)(.idata$6)
482     SORT(*)(.idata$7)
483   }
484   .CRT BLOCK(__section_alignment__) :
485   {
486     ___crt_xc_start__ = . ;
487     *(SORT(.CRT$XC*))  /* C initialization */
488     ___crt_xc_end__ = . ;
489     ___crt_xi_start__ = . ;
490     *(SORT(.CRT$XI*))  /* C++ initialization */
491     ___crt_xi_end__ = . ;
492     ___crt_xl_start__ = . ;
493     *(SORT(.CRT$XL*))  /* TLS callbacks */
494     /* ___crt_xl_end__ is defined in the TLS Directory support code */
495     ___crt_xp_start__ = . ;
496     *(SORT(.CRT$XP*))  /* Pre-termination */
497     ___crt_xp_end__ = . ;
498     ___crt_xt_start__ = . ;
499     *(SORT(.CRT$XT*))  /* Termination */
500     ___crt_xt_end__ = . ;
501   }
502   .tls BLOCK(__section_alignment__) :
503   {
504     ___tls_start__ = . ;
505     *(.tls)
506     *(.tls$)
507     *(SORT(.tls$*))
508     ___tls_end__ = . ;
509   }
510   .endjunk BLOCK(__section_alignment__) :
511   {
512     /* end is deprecated, don't use it */
513     PROVIDE (end = .);
514     PROVIDE ( _end = .);
515     __end__ = .;
516   }
517   .rsrc BLOCK(__section_alignment__) :
518   {
519     *(.rsrc)
520     *(SORT(.rsrc$*))
521   }
522   .reloc BLOCK(__section_alignment__) :
523   {
524     *(.reloc)
525   }
526   .stab BLOCK(__section_alignment__) (NOLOAD) :
527   {
528     *(.stab)
529   }
530   .stabstr BLOCK(__section_alignment__) (NOLOAD) :
531   {
532     *(.stabstr)
533   }
534   /* DWARF debug sections.
535     Symbols in the DWARF debugging sections are relative to the beginning
536     of the section.  Unlike other targets that fake this by putting the
537     section VMA at 0, the PE format will not allow it.  */
538   /* DWARF 1.1 and DWARF 2.  */
539   .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
540   {
541     *(.debug_aranges)
542   }
543   .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
544   {
545     *(.debug_pubnames)
546   }
547   /* DWARF 2.  */
548   .debug_info BLOCK(__section_alignment__) (NOLOAD) :
549   {
550     *(.debug_info) *(.gnu.linkonce.wi.*)
551   }
552   .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
553   {
554     *(.debug_abbrev)
555   }
556   .debug_line BLOCK(__section_alignment__) (NOLOAD) :
557   {
558     *(.debug_line)
559   }
560   .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
561   {
562     *(.debug_frame)
563   }
564   .debug_str BLOCK(__section_alignment__) (NOLOAD) :
565   {
566     *(.debug_str)
567   }
568   .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
569   {
570     *(.debug_loc)
571   }
572   .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
573   {
574     *(.debug_macinfo)
575   }
576   /* SGI/MIPS DWARF 2 extensions.  */
577   .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
578   {
579     *(.debug_weaknames)
580   }
581   .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
582   {
583     *(.debug_funcnames)
584   }
585   .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
586   {
587     *(.debug_typenames)
588   }
589   .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
590   {
591     *(.debug_varnames)
592   }
593   /* DWARF 3.  */
594   .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
595   {
596     *(.debug_ranges)
597   }
598 }
599 ''')
600     script.close()
601     return(ld_script)
602
603
604 try:
605     # these will be used under win32
606     import win32file
607     import win32event
608     import win32process
609     import win32security
610 except:
611     # does not matter if it fails on other systems
612     pass
613
614
615 class loggedSpawn:
616     def __init__(self, env, logfile, longarg, info):
617         # save the spawn system
618         self.env = env
619         self.logfile = logfile
620         # clear the logfile (it may not exist)
621         if logfile != '':
622             # this will overwrite existing content.
623             writeToFile(logfile, info, append=False)
624         #
625         self.longarg = longarg
626         # get hold of the old spawn? (necessary?)
627         self._spawn = env['SPAWN']
628
629     # define new SPAWN
630     def spawn(self, sh, escape, cmd, args, spawnenv):
631         # get command line
632         newargs = ' '.join(map(escape, args[1:]))
633         cmdline = cmd + " " + newargs
634         #
635         # if log is not empty, write to it
636         if self.logfile != '':
637             # this tend to be slow (?) but ensure correct output
638             # Note that cmdline may be long so I do not escape it
639             try:
640                 # since this is not an essential operation, proceed if things go wrong here.
641                 writeToFile(self.logfile, cmd + " " + ' '.join(args[1:]) + '\n', append=True)
642             except:
643                 print "Warning: can not write to log file ", self.logfile
644         #
645         # if the command is not too long, use the old
646         if not self.longarg or len(cmdline) < 8000:
647             exit_code = self._spawn(sh, escape, cmd, args, spawnenv)
648         else:
649             sAttrs = win32security.SECURITY_ATTRIBUTES()
650             StartupInfo = win32process.STARTUPINFO()
651             for var in spawnenv:
652                 spawnenv[var] = spawnenv[var].encode('ascii', 'replace')
653             # check for any special operating system commands
654             if cmd == 'del':
655                 for arg in args[1:]:
656                     win32file.DeleteFile(arg)
657                 exit_code = 0
658             else:
659                 # otherwise execute the command.
660                 hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(None, cmdline, None, None, 1, 0, spawnenv, None, StartupInfo)
661                 win32event.WaitForSingleObject(hProcess, win32event.INFINITE)
662                 exit_code = win32process.GetExitCodeProcess(hProcess)
663                 win32file.CloseHandle(hProcess);
664                 win32file.CloseHandle(hThread);
665         return exit_code
666
667
668 def setLoggedSpawn(env, logfile = '', longarg=False, info=''):
669     ''' This function modify env and allow logging of
670         commands to a logfile. If the argument is too long
671         a win32 spawn will be used instead of the system one
672     '''
673     #
674     # create a new spwn object
675     ls = loggedSpawn(env, logfile, longarg, info)
676     # replace the old SPAWN by the new function
677     env['SPAWN'] = ls.spawn
678