cca87a366a5f270a6cd0b2ff3b2d2642de3ad268
[senf.git] / site_scons / senfutil.py
1 import os.path, glob, site_tools.Yaptu
2 from SCons.Script import *
3 import SCons.Defaults, SCons.Util
4 import senfconf
5 import types, re
6
7 senfutildir = os.path.dirname(__file__)
8
9 # Fix for SCons 0.97 compatibility
10 try:
11     Variables
12 except NameError: 
13     Variables = Options
14     BoolVariable = BoolOption
15
16 def parseLogOption(value):
17     stream, area, level = ( x.strip() for x in value.strip().split('|') )
18     stream = ''.join('(%s)' % x for x in stream.split('::') )
19     if area : area = ''.join( '(%s)' % x for x in area.split('::') )
20     else    : area = '(_)'
21     return '((%s,%s,%s))' % (stream,area,level)
22
23 def expandLogOption(target, source, env, for_signature):
24     if env.subst('$LOGLEVELS'):
25         return [ 'SENF_LOG_CONF="' + ''.join( parseLogOption(x) for x in env.subst('$LOGLEVELS').split() )+'"']
26     else:
27         return []
28
29 class BuildTypeOptions:
30     def __init__(self, var):
31         self._var = var
32
33     def __call__(self, target, source, env, for_signature):
34         type = env['final'] and "final" or env['debug'] and "debug" or "normal"
35         return env[self._var + "_" + type]
36
37 _DOLLAR_RE = re.compile(r'\$([a-zA-Z_][\.\w]*)|\${([^}]*)}')
38
39 def _expandDefines(defs, target, source, env, for_signature):
40     rv = []
41     if SCons.Util.is_Dict(defs):
42         keys = defs.keys()
43         keys.sort()
44         defs = [ (k,defs[k]) for k in keys ]
45     elif not SCons.Util.is_List(defs):
46         defs = [ defs ]
47     for elt in defs:
48         if SCons.Util.is_String(elt):
49             m = _DOLLAR_RE.match(elt)
50             if m:
51                 match = m.group(1) or m.group(2)
52                 try: rv.extend(_expandDefines(eval(match, env.gvars(), env.lvars()),
53                                               target, source, env, for_signature))
54                 except NameError: pass
55                 except IndexError: pass
56             else:
57                 rv.append(env.subst(elt))
58         elif callable(elt):
59             rv.extend(_expandDefines(elt(target, source, env, for_signature),
60                                      target, source, env, for_signature))
61         elif SCons.Util.is_Sequence(elt):
62             if len(elt)<2 or elt[1] is None:
63                 rv.append(env.subst(elt[0]))
64             else:
65                 rv.append(env.subst(elt[0]) + "=" + env.subst(elt[1]))
66         else:
67             rv.append(str(elt))
68     return rv
69
70 def expandDefines(prefix, defs, suffix, env):
71     """Expand defines in <defs> using <env>. Calls SCons.Defaults._concat_ixes
72 to append prefix/suffix before/after each define.
73
74     callable
75         Call the callable and replace it with the call result. If the result
76         is a list, the list is processed recursively. It the result is a
77         dictionary it is converted into a list of typles and processed
78         recursively.
79     '$<variable>' or '${<variable>}'
80         Replace the element with the variable expansion. If the result is a
81         list, the list is processed recursively. It the result is a
82         dictionary it is converted into a list of typles and processed
83         recursively.
84     '<any other string>'
85         Define a symbol with that (expanded!) name
86     iterable
87         The iteratble must have two elments. The first element defines the
88         symbol name, the second the symbol value."""
89
90     defs = _expandDefines(defs, None, None, env, False)
91     return SCons.Defaults._concat_ixes(prefix, defs, suffix, env)
92     
93 def loadTools(env):
94     global senfutildir
95     tooldir = os.path.join(senfutildir, 'site_tools')
96     for tool in os.listdir(tooldir):
97         name, ext = os.path.splitext(tool)
98         if ext == '.py' and name != "__init__" : env.Tool(name, [ tooldir ])
99
100 def parseArguments(env, *defs):
101     vars = Variables(args=ARGUMENTS)
102     for d in defs : vars.Add(d)
103     vars.Update(env)
104     env.Help("""
105 Any construction environment variable may be set from the scons
106 command line (see SConstruct file and SCons documentation for a list
107 of variables) using
108
109    VARNAME=value    Assign new value  
110    VARNAME+=value   Append value at end
111
112 Special command line parameters:
113 """)
114     env.Help(vars.GenerateHelpText(env))
115     try                  : unknv = vars.UnknownVariables()
116     except AttributeError: unknv = vars.UnknownOptions()
117     env.SetDefault(ARGUMENT_VARIABLES = {})
118     for k,v in unknv.iteritems():
119         if k.endswith('+'):
120             env.Append(**{k[:-1]: v})
121             env.Append(ARGUMENT_VARIABLES = {k[:-1]:v})
122         else:
123             env.Replace(**{k: v})
124             env.Append(ARGUMENT_VARIABLES = {k:v})
125
126 def detect_senf(env,senf_path, try_flavors):
127     """Detect senf with flavor in 'try_flavors' somewhere in 'senf_path'.
128
129 This function returns True, if senf is found, False otherwise.
130 The environment 'env' is updated in the following way:
131
132     SENFSYSLAYOUT  set to True, if the install is a system installation,
133                    False otherwise
134
135     FLAVOR         set to the detected senf flavor
136
137     SENFDIR        set to the base directory of the senf installation.
138 """
139     global senfutildir
140     senf_path.extend((os.path.dirname(senfutildir), '/usr/local', '/usr'))
141
142     for path in senf_path:
143         if not path.startswith('/') : sconspath = '#/%s' % path
144         else                        : sconspath = path
145         for flavor in try_flavors:
146             suffix = flavor and "_"+flavor or ""
147             local_path = os.path.join(path,"senf%s.conf" % suffix)
148             sys_path = os.path.join(path, "lib", "senf", "senf%s.conf" % suffix)
149             if os.path.exists(local_path):
150                 env.SetDefault( SENFSYSLAYOUT = False )
151             elif os.path.exists(sys_path):
152                 env.SetDefault( SENFSYSLAYOUT  = True )
153             else:
154                 continue
155             env.SetDefault( FLAVOR = flavor, SENFDIR = sconspath )
156             return True
157     return False
158
159
160 def SetupForSENF(env, senf_path = [], flavor=None):
161     try_flavors = [ '', 'g' ]
162     if flavor is not None:
163         try_flavors[0:0] = [ flavor ]
164         
165     res = detect_senf(env, senf_path, try_flavors)
166     if not env.GetOption('no_progress'):
167         if res:
168             print env.subst("scons: Using${SENFSYSLAYOUT and ' system' or ''} 'libsenf${LIBADDSUFFIX}' in '$SENFDIR'")
169         else:
170             print "scons: SENF library not found, trying to build anyway ..."
171
172     loadTools(env)
173
174     # For a non-system installed senf, we add library and include search path here
175     if not env.get('SENFSYSLAYOUT', True):
176         env.Append(
177             CPPPATH       = [ '$SENFINCDIR' ],
178             LIBPATH       = [ '$SENFDIR' ],
179             )
180     
181     env.Replace(
182         _defines          = expandDefines,
183         expandLogOption   = expandLogOption,
184         )
185     env.SetDefault(
186         FLAVOR            = flavor,
187         LIBADDSUFFIX      = '${FLAVOR and "_$FLAVOR" or ""}',
188         OBJADDSUFFIX      = '${LIBADDSUFFIX}',
189         PROJECTNAME       = "Unnamed project",
190         DOCLINKS          = [],
191         PROJECTEMAIL      = "nobody@nowhere.org",
192         COPYRIGHT         = "nobody",
193         REVISION          = "unknown",
194         BUNDLEDIR         = '$SENFDIR${SENFSYSLAYOUT and "/lib/senf" or ""}',
195         SENFINCDIR        = '$SENFDIR${SENFSYSLAYOUT and "/include" or ""}',
196         )
197     env.Append(
198         CPPPATH           = [ '${NEED_BOOST_EXT and "$SENFINCDIR/boost_ext" or None}' ],
199         CPPDEFINES        = [ '$expandLogOption' ],
200         CXXFLAGS          = [ '-Wno-long-long', '-fno-strict-aliasing' ],
201         LIBS              = [ 'senf$LIBADDSUFFIX', 'rt',
202                               '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', '$BOOSTFSLIB' ],
203         LINKFLAGS         = [ '-rdynamic' ],
204         )
205         
206     try:
207         env.MergeFlags(file(env.File('$BUNDLEDIR/senf${LIBADDSUFFIX}.conf').abspath).read())
208     except IOError:
209         # Really should never happen since detect_senf looks for this file ...
210         pass
211
212 def DefaultOptions(env):
213     env.Replace(
214         _defines          = expandDefines,
215         expandLogOption   = expandLogOption,
216         )
217     env.Append(
218         CXXFLAGS          = [ '$CXXFLAGS_' ],
219         CXXFLAGS_         = BuildTypeOptions('CXXFLAGS'),
220         
221         CPPDEFINES        = [ '$CPPDEFINES_' ],
222         CPPDEFINES_       = BuildTypeOptions('CPPDEFINES'),
223         
224         LINKFLAGS         = [ '$LINKFLAGS_' ],
225         LINKFLAGS_        = BuildTypeOptions('LINKFLAGS'),
226
227         LOGLEVELS         = [ '$LOGLEVELS_' ],
228         LOGLEVELS_        = BuildTypeOptions('LOGLEVELS'),
229         )
230     env.SetDefault( 
231         CXXFLAGS_final    = [],
232         CXXFLAGS_normal   = [],
233         CXXFLAGS_debug    = [],
234
235         CPPDEFINES_final  = [],
236         CPPDEFINES_normal = [],
237         CPPDEFINES_debug  = [],
238
239         LINKFLAGS_final   = [],
240         LINKFLAGS_normal  = [],
241         LINKFLAGS_debug   = [],
242
243         LOGLEVELS_final   = [],
244         LOGLEVELS_normal  = [],
245         LOGLEVELS_debug   = [],
246         )
247
248     # Interpret command line options
249     parseArguments(
250         env, 
251         BoolVariable('final', 'Build final (optimized) build', False),
252         BoolVariable('debug', 'Link in debug symbols', False),
253     )
254
255     # Set nice default options
256     env.Append(
257         CXXFLAGS         = [ '-Wall', '-Woverloaded-virtual' ],
258         CXXFLAGS_final   = [ '-O3' ],
259         CXXFLAGS_normal  = [ '-O2', '-g' ],
260         CXXFLAGS_debug   = [ '-O0', '-g' ],
261
262         LINKFLAGS_normal = [ '-Wl,-S' ],
263         LINKFLAGS_debug  = [ '-g' ],
264     )
265
266     env.Alias('all', '#')
267
268
269 def Glob(env, exclude=[], subdirs=[]):
270     testSources = env.Glob("*.test.cc", strings=True)
271     sources = [ x 
272                 for x in env.Glob("*.cc", strings=True) 
273                 if x not in testSources and x not in exclude ]
274     for subdir in subdirs:
275         testSources += env.Glob(os.path.join(subdir,"*.test.cc"), strings=True)
276         sources += [ x 
277                      for x in env.Glob(os.path.join(subdir,"*.cc"), strings=True)
278                      if x not in testSources and x not in exclude ]
279     sources.sort()
280     testSources.sort()
281     return (sources, testSources)
282
283
284 @senfconf.Test
285 def CheckSTLCopyN(context):
286     context.Message("Checking for 'copy_n' implementation... ")
287     versions = [ ('<algorithm>',     'std::copy_n',       'STD'),
288                  ('<ext/algorithm>', '__gnu_cxx::copy_n', 'GNUCXX') ]
289     for include, name, define in versions:
290         ret = context.TryCompile("#include %s\n"
291                                  "int main(int,char**) { int *a,*b; %s(a,0,b); }\n"
292                                  % (include, name),
293                                  ".cc")
294         if ret:
295             context.Result(name)
296             context.sconf.Define("HAVE_%s_COPYN" % define,
297                                  1,
298                                  "Define one of " 
299                                  + ", ".join(("HAVE_%s_COPYN" % elt[2] for elt in versions)))
300             return ret
301
302     context.Result(False)
303     return False
304
305
306 @senfconf.Test
307 def CheckTempBufferStrategy(context):
308     context.Message("Checking for optimal temporary buffer strategy... ")
309
310     def check():
311       # locals
312       ret = context.TryCompile("void test(int n){int a[n];}",".cc")
313       if ret: return "locals"
314
315       # alloca
316       ret = context.TryCompile("#include <alloca.h>\n"
317                                "void test(int a){void *b(alloca(a));}"
318                                ".cc")
319       if ret: return "alloca"
320       
321       # fallback: new
322       return "new"
323
324     ret = check()
325     context.Result(ret)
326     context.sconf.Define("SENF_BUFFER_USE_%s" % ret.upper(),
327                          1,
328                          "Define one of SENF_BUFFER_USE_LOCALS, SENF_BUFFER_USE_ALLOCA, "
329                          "SENF_BUFFER_USE_NEW")
330     return ret
331
332
333 def Fail(msg):
334     SCons.Util.display("scons: *** %s" % msg)
335     Exit(1)
336
337
338 def Configure(env, customChecks=None):
339     conf = env.Configure(clean=False, 
340                          help=False, 
341                          custom_tests=senfconf.Tests(), 
342                          config_h="#/senf/autoconf.hh")
343
344     # Boost
345     if not conf.CheckBoostVersion():
346         Fail("Boost includes not found")
347     if not conf.env['ARGUMENT_VARIABLES'].has_key('BOOST_VARIANT'): conf.CheckBoostVariants( '', 'mt' )
348     conf.env.Replace(NEED_BOOST_EXT = not conf.CheckCXXHeader("boost/bimap.hpp"))
349     conf.CheckCXXHeader("boost/spirit/include/classic.hpp")
350         
351     # Compiler support
352     conf.CheckTempBufferStrategy()
353
354     # Standard library stuff
355     if not conf.CheckSTLCopyN():
356         Fail("No 'copy_n' implementation found")
357     conf.CheckFunc("timerfd_create")
358
359     # User checks
360     if customChecks:
361         customChecks(conf)
362
363     conf.Finish()
364
365 tagfiles = None
366
367 def Doxygen(env, doxyheader=None, doxyfooter=None, doxycss=None, mydoxyfile=False, senfdoc_path=[],
368             **kw):
369     # Additional interesting keyword arguments or environment variables:
370     #    PROJECTNAME, DOCLINKS, PROJECTEMAIL, COPYRIGHT, REVISION
371
372     global senfutildir
373     global tagfiles
374     libdir=os.path.join(senfutildir, 'lib')
375     
376     if tagfiles is None:
377         senfdocdir = None
378         senfdoc_path.extend(('senfdoc', '$SENFDIR', '$SENFDIR/manual',
379                              '$SENFDIR/share/doc/senf', '$SENFDIR/share/doc/libsenf-doc/html'))
380         for path in senfdoc_path:
381             path = env.Dir(path).get_path()
382             if os.path.exists(os.path.join(path, "doc/doclib.tag")):
383                 senfdocdir = path
384                 break
385         tagfiles = []
386         if senfdocdir is None:
387             if not env.GetOption('no_progress'):
388                 print "(SENF documentation not found)"
389         else:
390             for dir, dirs, files in os.walk(senfdocdir):
391                 tagfiles.extend([ os.path.join(dir,f) for f in files if f.endswith('.tag') ])
392                 if dir.endswith('/doc') : 
393                     try: dirs.remove('html')
394                     except ValueError: pass
395                 for d in dirs: 
396                     if d.startswith('.') : dirs.remove(d)
397     
398     if env.GetOption('clean'):
399         env.Clean('doc', env.Dir('doc'))
400         if not mydoxyfile:
401             env.Clean('doc', "Doxyfile")
402
403     if not mydoxyfile:
404         # Create Doxyfile NOW
405         site_tools.Yaptu.yaptuAction("Doxyfile", 
406                                      os.path.join(libdir, "Doxyfile.yap"),
407                                      env)
408
409     envvalues = [ env.Value('$PROJECTNAME'),
410                   env.Value('$DOCLINKS'),
411                   env.Value('$PROJECTEMAIL'),
412                   env.Value('$COPYRIGHT'),
413                   env.Value('$REVISION') ]
414
415     # The other files are created using dependencies
416     if doxyheader: 
417         doxyheader = env.CopyToDir(env.Dir("doc"), doxyheader)
418     else:
419         doxyheader = env.Yaptu("doc/doxyheader.html", os.path.join(libdir, "doxyheader.yap"), **kw)
420         env.Depends(doxyheader, envvalues)
421     if doxyfooter:
422         doxyfooter = env.CopyToDir(env.Dir("doc"), doxyfooter)
423     else:
424         doxyfooter = env.Yaptu("doc/doxyfooter.html", os.path.join(libdir, "doxyfooter.yap"), **kw)
425         env.Depends(doxyfooter, envvalues)
426     if doxycss:
427         doxycss = env.CopyToDir(env.Dir("doc"), doxycss)
428     else:
429         doxycss    = env.CopyToDir(env.Dir("doc"), os.path.join(libdir, "doxy.css"))
430
431     doc = env.Doxygen("Doxyfile",
432                       DOXYOPTS   = [ '--html', '--tagfiles', '"$TAGFILES"' ],
433                       DOXYENV    = { 'TOPDIR'     : env.Dir('#').abspath,
434                                      'LIBDIR'     : libdir,
435                                      'REVISION'   : '$REVISION',
436                                      'tagfiles'   : '$TAGFILES',
437                                      'output_dir' : 'doc',
438                                      'html_dir'   : 'html',
439                                      'html'       : 'YES',
440                                      'DOXYGEN'    : '$DOXYGEN' },
441                       TAGFILES   = tagfiles, 
442                       DOCLIBDIR  = libdir,
443                       DOXYGENCOM = "$DOCLIBDIR/doxygen.sh $DOXYOPTS $SOURCE")
444
445     env.Depends(doc, [ doxyheader, doxyfooter, doxycss ])
446
447     return doc