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