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