Rework configure stuff
[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.get('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     for k,v in unknv.iteritems():
118         if k.endswith('+'):
119             env.Append(**{k[:-1]: v})
120         else:
121             env.Replace(**{k: v})
122
123
124 ###########################################################################
125 # This looks much more complicated than it is: We do three things here:
126 # a) switch between final or debug options
127 # b) parse the LOGLEVELS parameter into the correct SENF_LOG_CONF syntax
128 # c) check for a local SENF, set options accordingly
129 # d) check, wether the boost extensions are needed
130
131 def SetupForSENF(env, senf_path = []):
132     global senfutildir
133     senf_path.extend(('senf', os.path.dirname(senfutildir), '/usr/local', '/usr'))
134
135     loadTools(env)
136
137     env.Append(
138         LIBS              = [ 'rt' ],
139         
140         CXXFLAGS          = [ '-Wno-long-long', '$CXXFLAGS_', '-fno-strict-aliasing' ],
141         CXXFLAGS_         = BuildTypeOptions('CXXFLAGS'),
142         
143         CPPDEFINES        = [ '$expandLogOption', '$CPPDEFINES_' ],
144         expandLogOption   = expandLogOption,
145         CPPDEFINES_       = BuildTypeOptions('CPPDEFINES'),
146         
147         LINKFLAGS         = [ '-rdynamic', '$LINKFLAGS_' ],
148         LINKFLAGS_        = BuildTypeOptions('LINKFLAGS'),
149
150         LOGLEVELS         = [ '$LOGLEVELS_' ],
151         LOGLEVELS_        = BuildTypeOptions('LOGLEVELS'),
152         )
153
154     env.SetDefault( 
155         CXXFLAGS_final    = [],
156         CXXFLAGS_normal   = [],
157         CXXFLAGS_debug    = [],
158
159         CPPDEFINES_final  = [],
160         CPPDEFINES_normal = [],
161         CPPDEFINES_debug  = [],
162
163         LINKFLAGS_final   = [],
164         LINKFLAGS_normal  = [],
165         LINKFLAGS_debug   = [],
166
167         LOGLEVELS_final   = [],
168         LOGLEVELS_normal  = [],
169         LOGLEVELS_debug   = [],
170
171         PROJECTNAME       = "Unnamed project",
172         DOCLINKS          = [],
173         PROJECTEMAIL      = "nobody@nowhere.org",
174         COPYRIGHT         = "nobody",
175         REVISION          = "unknown",
176         )
177
178     env.Replace( _defines = expandDefines )
179
180     # Interpret command line options
181     parseArguments(
182         env, 
183         BoolVariable('final', 'Build final (optimized) build', False),
184         BoolVariable('debug', 'Link in debug symbols', False),
185     )
186
187     # If we have a symbolic link (or directory) 'senf', we use it as our
188     # senf repository
189     for path in senf_path:
190         if not path.startswith('/') : sconspath = '#/%s' % path
191         else                        : sconspath = path
192         if os.path.exists(os.path.join(path,"senf/config.hh")):
193             if not env.GetOption('no_progress'):
194                 print "\nUsing SENF in '%s'\n" \
195                     % ('/..' in sconspath and os.path.abspath(path) or sconspath)
196             env.Append( LIBPATH = [ sconspath ],
197                         CPPPATH = [ sconspath ],
198                         BUNDLEDIR = sconspath,
199                         SENFDIR = sconspath,
200                         SENFINCDIR = sconspath,
201                         SENFSYSLAYOUT = False)
202             try:
203                 env.MergeFlags(file(os.path.join(path,"senf.conf")).read())
204             except IOError:
205                 if not env.GetOption('no_progress'):
206                     print "(SENF configuration file 'senf.conf' not found, assuming non-final SENF)"
207                 env.Append(CPPDEFINES = [ 'SENF_DEBUG' ])
208             break
209         elif os.path.exists(os.path.join(path,"include/senf/config.hh")):
210             if not env.GetOption('no_progress'):
211                 print "\nUsing system SENF in '%s/'\n" % sconspath
212             env.Append(BUNDLEDIR = os.path.join(sconspath,"lib/senf"),
213                        SENFDIR = sconspath,
214                        SENFINCDIR = '%s/include' % sconspath,
215                        SENFSYSLAYOUT = True)
216             break
217     else:
218         if not env.GetOption('no_progress'):
219             print "\nSENF library not found .. trying build anyway !!\n"
220
221     Configure(env)
222
223     # Only add senf after all configure checks have run
224     env.Append(
225         CPPPATH = '${NEED_BOOST_EXT and "$SENFINCDIR/boost_ext" or None}',
226         LIBS = [ 'senf', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB',
227                  '$BOOSTFSLIB' ],
228         )
229
230     env.Alias('all', '#')
231
232
233 def DefaultOptions(env):
234     env.Append(
235         CXXFLAGS         = [ '-Wall', '-Woverloaded-virtual' ],
236         CXXFLAGS_final   = [ '-O3' ],
237         CXXFLAGS_normal  = [ '-O2', '-g' ],
238         CXXFLAGS_debug   = [ '-O0', '-g' ],
239
240         LINKFLAGS_normal = [ '-Wl,-S' ],
241         LINKFLAGS_debug  = [ '-g' ],
242     )
243
244
245 def Glob(env, exclude=[], subdirs=[]):
246     testSources = env.Glob("*.test.cc", strings=True)
247     sources = [ x 
248                 for x in env.Glob("*.cc", strings=True) 
249                 if x not in testSources and x not in exclude ]
250     for subdir in subdirs:
251         testSources += env.Glob(os.path.join(subdir,"*.test.cc"), strings=True)
252         sources += [ x 
253                      for x in env.Glob(os.path.join(subdir,"*.cc"), strings=True)
254                      if x not in testSources and x not in exclude ]
255     sources.sort()
256     testSources.sort()
257     return (sources, testSources)
258
259
260 @senfconf.Test
261 def CheckSTLCopyN(context):
262     context.Message("Checking for 'copy_n' implementation... ")
263     versions = [ ('<algorithm>',     'std::copy_n',       'STD'),
264                  ('<ext/algorithm>', '__gnu_cxx::copy_n', 'GNUCXX') ]
265     for include, name, define in versions:
266         ret = context.TryCompile("#include %s\n"
267                                  "int main(int,char**) { int *a,*b; %s(a,0,b); }\n"
268                                  % (include, name),
269                                  ".cc")
270         if ret:
271             context.Result(name)
272             context.sconf.Define("HAVE_%s_COPYN" % define,
273                                  1,
274                                  "Define one of " 
275                                  + ", ".join(("HAVE_%s_COPYN" % elt[2] for elt in versions)))
276             return ret
277
278     context.Result(False)
279     return False
280
281
282 @senfconf.Test
283 def CheckTempBufferStrategy(context):
284     context.Message("Checking for optimal temporary buffer strategy... ")
285
286     def check():
287       # locals
288       ret = context.TryCompile("void test(int n){int a[n];}",".cc")
289       if ret: return "locals"
290
291       # alloca
292       ret = context.TryCompile("#include <alloca.h>\n"
293                                "void test(int a){void *b(alloca(a));}"
294                                ".cc")
295       if ret: return "alloca"
296       
297       # fallback: new
298       return "new"
299
300     ret = check()
301     context.Result(ret)
302     context.sconf.Define("SENF_BUFFER_USE_%s" % ret.upper(),
303                          1,
304                          "Define one of SENF_BUFFER_USE_LOCALS, SENF_BUFFER_USE_ALLOCA, "
305                          "SENF_BUFFER_USE_NEW")
306     return ret
307
308
309 def Fail(msg):
310     SCons.Util.display("scons: *** %s" % msg)
311     Exit(1)
312
313
314 def Configure(env, customChecks=None):
315     conf = env.Configure(clean=False, 
316                          help=False, 
317                          custom_tests=senfconf.Tests(), 
318                          config_h="#/senf/autoconf.hh")
319
320     # Boost
321     if not conf.CheckBoostVersion():
322         Fail("Boost includes not found")
323     if not conf.env['ARGUMENT_VARIABLES'].has_key('BOOST_VARIANT'): conf.CheckBoostVariants( '', 'mt' )
324     conf.env.Replace(NEED_BOOST_EXT = not conf.CheckCXXHeader("boost/bimap.hpp"))
325     conf.CheckCXXHeader("boost/spirit/include/classic.hpp")
326         
327     # Compiler support
328     conf.CheckTempBufferStrategy()
329
330     # Standard library stuff
331     if not conf.CheckSTLCopyN():
332         Fail("No 'copy_n' implementation found")
333     conf.CheckFunc("timerfd_create")
334
335     # User checks
336     if customChecks:
337         customChecks(conf)
338
339     conf.Finish()
340
341 tagfiles = None
342
343 def Doxygen(env, doxyheader=None, doxyfooter=None, doxycss=None, mydoxyfile=False, senfdoc_path=[],
344             **kw):
345     # Additional interesting keyword arguments or environment variables:
346     #    PROJECTNAME, DOCLINKS, PROJECTEMAIL, COPYRIGHT, REVISION
347
348     global senfutildir
349     global tagfiles
350     libdir=os.path.join(senfutildir, 'lib')
351     
352     if tagfiles is None:
353         senfdocdir = None
354         senfdoc_path.extend(('senfdoc', '$SENFDIR', '$SENFDIR/manual',
355                              '$SENFDIR/share/doc/senf', '$SENFDIR/share/doc/libsenf-doc/html'))
356         for path in senfdoc_path:
357             path = env.Dir(path).get_path()
358             if os.path.exists(os.path.join(path, "doc/doclib.tag")):
359                 senfdocdir = path
360                 break
361         tagfiles = []
362         if senfdocdir is None:
363             if not env.GetOption('no_progress'):
364                 print "(SENF documentation not found)"
365         else:
366             for dir, dirs, files in os.walk(senfdocdir):
367                 tagfiles.extend([ os.path.join(dir,f) for f in files if f.endswith('.tag') ])
368                 if dir.endswith('/doc') : 
369                     try: dirs.remove('html')
370                     except ValueError: pass
371                 for d in dirs: 
372                     if d.startswith('.') : dirs.remove(d)
373     
374     if env.GetOption('clean'):
375         env.Clean('doc', env.Dir('doc'))
376         if not mydoxyfile:
377             env.Clean('doc', "Doxyfile")
378
379     if not mydoxyfile:
380         # Create Doxyfile NOW
381         site_tools.Yaptu.yaptuAction("Doxyfile", 
382                                      os.path.join(libdir, "Doxyfile.yap"),
383                                      env)
384
385     envvalues = [ env.Value('$PROJECTNAME'),
386                   env.Value('$DOCLINKS'),
387                   env.Value('$PROJECTEMAIL'),
388                   env.Value('$COPYRIGHT'),
389                   env.Value('$REVISION') ]
390
391     # The other files are created using dependencies
392     if doxyheader: 
393         doxyheader = env.CopyToDir(env.Dir("doc"), doxyheader)
394     else:
395         doxyheader = env.Yaptu("doc/doxyheader.html", os.path.join(libdir, "doxyheader.yap"), **kw)
396         env.Depends(doxyheader, envvalues)
397     if doxyfooter:
398         doxyfooter = env.CopyToDir(env.Dir("doc"), doxyfooter)
399     else:
400         doxyfooter = env.Yaptu("doc/doxyfooter.html", os.path.join(libdir, "doxyfooter.yap"), **kw)
401         env.Depends(doxyfooter, envvalues)
402     if doxycss:
403         doxycss = env.CopyToDir(env.Dir("doc"), doxycss)
404     else:
405         doxycss    = env.CopyToDir(env.Dir("doc"), os.path.join(libdir, "doxy.css"))
406
407     doc = env.Doxygen("Doxyfile",
408                       DOXYOPTS   = [ '--html', '--tagfiles', '"$TAGFILES"' ],
409                       DOXYENV    = { 'TOPDIR'     : env.Dir('#').abspath,
410                                      'LIBDIR'     : libdir,
411                                      'REVISION'   : '$REVISION',
412                                      'tagfiles'   : '$TAGFILES',
413                                      'output_dir' : 'doc',
414                                      'html_dir'   : 'html',
415                                      'html'       : 'YES',
416                                      'DOXYGEN'    : '$DOXYGEN' },
417                       TAGFILES   = tagfiles, 
418                       DOCLIBDIR  = libdir,
419                       DOXYGENCOM = "$DOCLIBDIR/doxygen.sh $DOXYOPTS $SOURCE")
420
421     env.Depends(doc, [ doxyheader, doxyfooter, doxycss ])
422
423     return doc