Fix CPPDFINES handling in scons
[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 def Configure(env):
261     conf = env.Configure(clean=False, 
262                          help=False, 
263                          custom_tests=senfconf.Tests(), 
264                          config_h="#/senf/autoconf.hh")
265     env.Replace(
266         BOOST_VERSION  =  conf.CheckBoostVersion(),
267         BOOST_VARIANT  = conf.CheckBoostVariants( '', 'mt' ),
268         NEED_BOOST_EXT = not conf.CheckCXXHeader("boost/bimap.hpp"),
269         HAVE_BOOST_SPIRIT_INCLUDE_CLASSIC_HPP = conf.CheckCXXHeader(
270             "boost/spirit/include/classic.hpp"),
271     )
272     conf.Finish()
273
274
275 tagfiles = None
276
277 def Doxygen(env, doxyheader=None, doxyfooter=None, doxycss=None, mydoxyfile=False, senfdoc_path=[],
278             **kw):
279     # Additional interesting keyword arguments or environment variables:
280     #    PROJECTNAME, DOCLINKS, PROJECTEMAIL, COPYRIGHT, REVISION
281
282     global senfutildir
283     global tagfiles
284     libdir=os.path.join(senfutildir, 'lib')
285     
286     if tagfiles is None:
287         senfdocdir = None
288         senfdoc_path.extend(('senfdoc', '$SENFDIR', '$SENFDIR/manual',
289                              '$SENFDIR/share/doc/senf', '$SENFDIR/share/doc/libsenf-doc/html'))
290         for path in senfdoc_path:
291             path = env.Dir(path).get_path()
292             if os.path.exists(os.path.join(path, "doc/doclib.tag")):
293                 senfdocdir = path
294                 break
295         tagfiles = []
296         if senfdocdir is None:
297             if not env.GetOption('no_progress'):
298                 print "(SENF documentation not found)"
299         else:
300             for dir, dirs, files in os.walk(senfdocdir):
301                 tagfiles.extend([ os.path.join(dir,f) for f in files if f.endswith('.tag') ])
302                 if dir.endswith('/doc') : 
303                     try: dirs.remove('html')
304                     except ValueError: pass
305                 for d in dirs: 
306                     if d.startswith('.') : dirs.remove(d)
307     
308     if env.GetOption('clean'):
309         env.Clean('doc', env.Dir('doc'))
310         if not mydoxyfile:
311             env.Clean('doc', "Doxyfile")
312
313     if not mydoxyfile:
314         # Create Doxyfile NOW
315         site_tools.Yaptu.yaptuAction("Doxyfile", 
316                                      os.path.join(libdir, "Doxyfile.yap"),
317                                      env)
318
319     envvalues = [ env.Value('$PROJECTNAME'),
320                   env.Value('$DOCLINKS'),
321                   env.Value('$PROJECTEMAIL'),
322                   env.Value('$COPYRIGHT'),
323                   env.Value('$REVISION') ]
324
325     # The other files are created using dependencies
326     if doxyheader: 
327         doxyheader = env.CopyToDir(env.Dir("doc"), doxyheader)
328     else:
329         doxyheader = env.Yaptu("doc/doxyheader.html", os.path.join(libdir, "doxyheader.yap"), **kw)
330         env.Depends(doxyheader, envvalues)
331     if doxyfooter:
332         doxyfooter = env.CopyToDir(env.Dir("doc"), doxyfooter)
333     else:
334         doxyfooter = env.Yaptu("doc/doxyfooter.html", os.path.join(libdir, "doxyfooter.yap"), **kw)
335         env.Depends(doxyfooter, envvalues)
336     if doxycss:
337         doxycss = env.CopyToDir(env.Dir("doc"), doxycss)
338     else:
339         doxycss    = env.CopyToDir(env.Dir("doc"), os.path.join(libdir, "doxy.css"))
340
341     doc = env.Doxygen("Doxyfile",
342                       DOXYOPTS   = [ '--html', '--tagfiles', '"$TAGFILES"' ],
343                       DOXYENV    = { 'TOPDIR'     : env.Dir('#').abspath,
344                                      'LIBDIR'     : libdir,
345                                      'REVISION'   : '$REVISION',
346                                      'tagfiles'   : '$TAGFILES',
347                                      'output_dir' : 'doc',
348                                      'html_dir'   : 'html',
349                                      'html'       : 'YES',
350                                      'DOXYGEN'    : '$DOXYGEN' },
351                       TAGFILES   = tagfiles, 
352                       DOCLIBDIR  = libdir,
353                       DOXYGENCOM = "$DOCLIBDIR/doxygen.sh $DOXYOPTS $SOURCE")
354
355     env.Depends(doc, [ doxyheader, doxyfooter, doxycss ])
356
357     return doc