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