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