]> git.lyx.org Git - features.git/blob - development/scons/scons_utils.py
Scons: config.h moves to BUILDDIR/common, boost_config.h moves to BUILDDIR/boost.
[features.git] / development / scons / scons_utils.py
1 # vi:filetype=python:expandtab:tabstop=2:shiftwidth=2
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