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