minor fixes for clang++
[senf.git] / site_scons / senfutil.py
1 import os, os.path, site_tools.Yaptu, types, re, fnmatch, sys
2 import SCons.Util
3 from SCons.Script import *
4
5 senfutildir = os.path.dirname(__file__)
6
7 # Fix for SCons 0.97 compatibility
8 try:
9     Variables
10 except NameError:
11     Variables = Options
12     BoolVariable = BoolOption
13
14 ###########################################################################
15 extdir = os.path.join(senfutildir, '../senf/Ext')
16 sys.path.append(extdir)
17
18 for ext in os.listdir(extdir):
19     if not os.path.isdir( os.path.join(extdir, ext)): continue
20     if ext.startswith('.'): continue
21     try:
22         setattr( sys.modules[__name__], ext, 
23                 __import__('%s.site_scons' % ext, fromlist=['senfutil']).senfutil )
24     except ImportError:
25         pass
26
27 ###########################################################################
28
29 def loadTools(env):
30     global senfutildir
31     tooldir = os.path.join(senfutildir, 'site_tools')
32     for tool in os.listdir(tooldir):
33         name, ext = os.path.splitext(tool)
34         if ext == '.py' and name != "__init__" : env.Tool(name, [ tooldir ])
35
36 def parseArguments(env, *defs):
37     vars = Variables(args=ARGUMENTS)
38     for d in defs : vars.Add(d)
39     vars.Update(env)
40     env.Help("""
41 Any construction environment variable may be set from the scons
42 command line (see SConstruct file and SCons documentation for a list
43 of variables) using
44
45    VARNAME=value    Assign new value
46    VARNAME+=value   Append value at end
47
48 Special command line parameters:
49 """)
50     env.Help(vars.GenerateHelpText(env))
51     try                  : unknv = vars.UnknownVariables()
52     except AttributeError: unknv = vars.UnknownOptions()
53     env.SetDefault(ARGUMENT_VARIABLES = {})
54     for k,v in ARGLIST:
55         if not unknv.has_key(k) : continue
56         if k.endswith('+'):
57             env.Append(**{k[:-1]: v})
58             env.Append(ARGUMENT_VARIABLES = {k[:-1]:v})
59         else:
60             env.Replace(**{k: v})
61             env.Append(ARGUMENT_VARIABLES = {k:v})
62     if env.get('PARSEFLAGS', None):
63         env.MergeFlags(env['PARSEFLAGS'])
64
65 def importProcessEnv(env):
66     env.Append( ENV = dict(( (k,v)
67                              for pattern in env.get('IMPORT_ENV',[])
68                              for k,v in os.environ.iteritems()
69                              if fnmatch.fnmatchcase(k,pattern) )) )
70
71
72 ###########################################################################$
73 # SENF log option parsing
74
75 def parseLogOption(value):
76     stream, area, level = ( x.strip() for x in value.strip().split('|') )
77     stream = ''.join('(%s)' % x for x in stream.split('::') )
78     if area : area = ''.join( '(%s)' % x for x in area.split('::') )
79     else    : area = '(_)'
80     return '((%s,%s,%s))' % (stream,area,level)
81
82 def expandLogOption(target, source, env, for_signature):
83     if env.subst('$LOGLEVELS'):
84         return [ 'SENF_LOG_CONF="' + ''.join( parseLogOption(x) for x in env.subst('$LOGLEVELS').split() )+'"']
85     else:
86         return []
87
88 ###########################################################################
89 # client SENF detection/configuration
90
91 def detect_senf(env,senf_path, try_flavors):
92     """Detect senf with flavor in 'try_flavors' somewhere in 'senf_path'.
93
94 This function returns True, if senf is found, False otherwise.
95 The environment 'env' is updated in the following way:
96
97     SENFSYSLAYOUT  set to True, if the install is a system installation,
98                    False otherwise
99
100     FLAVOR         set to the detected senf flavor
101
102     SENFDIR        set to the base directory of the senf installation.
103 """
104     global senfutildir
105     senf_path.extend((os.path.dirname(senfutildir), '/usr/local', '/usr'))
106
107     for path in senf_path:
108         if not path.startswith('/') : sconspath = '#/%s' % path
109         else                        : sconspath = path
110         for flavor in try_flavors:
111             suffix = flavor and "_"+flavor or ""
112             local_path = os.path.join(path,"senf%s.conf" % suffix)
113             sys_path = os.path.join(path, "lib", "senf", "senf%s.conf" % suffix)
114             if os.path.exists(local_path):
115                 env.SetDefault( SENFSYSLAYOUT = False )
116             elif os.path.exists(sys_path):
117                 env.SetDefault( SENFSYSLAYOUT  = True )
118             else:
119                 continue
120             env.SetDefault( FLAVOR = flavor, SENFDIR = sconspath )
121             return True
122     return False
123
124 def SetupForSENF(env, senf_path = [], flavor=None):
125     try_flavors = [ '', 'g' ]
126     if flavor is not None:
127         try_flavors[0:0] = [ flavor ]
128
129     res = detect_senf(env, senf_path, try_flavors)
130     if res:
131         if not env.GetOption('no_progress'):
132             print env.subst("scons: Using${SENFSYSLAYOUT and ' system' or ''} "
133                             "'libsenf${LIBADDSUFFIX}' in '$SENFDIR'")
134     else:
135         print "scons: SENF library not found, trying to build anyway ..."
136
137     loadTools(env)
138
139     # For a non-system installed senf, we add library and include search path here
140     if not env.get('SENFSYSLAYOUT', True):
141         env.Append(
142             CPPPATH       = [ '$SENFINCDIR' ],
143             LIBPATH       = [ '$SENFDIR' ],
144             )
145
146     if env['BOOST_VARIANT'] is None:
147         conf = env.Configure(clean=False, help=False)
148         conf.CheckBoostVersion(fail=True)
149         conf.CheckBoostVariants()
150         conf.Finish()
151
152     env.Replace(
153         expandLogOption   = expandLogOption,
154         )
155     env.SetDefault(
156         LIBADDSUFFIX      = '${FLAVOR and "_$FLAVOR" or ""}',
157         OBJADDSUFFIX      = '${LIBADDSUFFIX}',
158         BUNDLEDIR         = '$SENFDIR${SENFSYSLAYOUT and "/lib/senf" or ""}',
159         SENFINCDIR        = '$SENFDIR${SENFSYSLAYOUT and "/include" or ""}',
160
161         PROJECTNAME       = "Unnamed project",
162         DOCLINKS          = [],
163         PROJECTEMAIL      = "nobody@nowhere.org",
164         COPYRIGHT         = "nobody",
165         REVISION          = "unknown",
166         )
167     env.Append(
168         CPPDEFINES        = [ '$expandLogOption' ],
169         CXXFLAGS          = [ '-Wno-long-long', '-fno-strict-aliasing' ],
170         LINKFLAGS         = [ '-rdynamic' ],
171         LIBS              = [ 'senf$LIBADDSUFFIX', 'rt', '$BOOSTREGEXLIB',
172                               '$BOOSTSIGNALSLIB', '$BOOSTFSLIB', '$BOOSTSYSTEMLIB',
173                               '$BOOSTDATETIMELIB' ],
174         )
175
176     try:
177         path = env.File('$BUNDLEDIR/senf${LIBADDSUFFIX}.conf').abspath
178         env.MergeFlags(file(path).read())
179     except IOError:
180         # Really should never happen since detect_senf looks for this file ...
181         pass
182
183 ###########################################################################
184 # Helpers
185
186 def DefaultOptions(env):
187     env.Replace(
188         expandLogOption   = expandLogOption,
189         CXXFLAGS_         = env.BuildTypeOptions('CXXFLAGS'),
190         CPPDEFINES_       = env.BuildTypeOptions('CPPDEFINES'),
191         LINKFLAGS_        = env.BuildTypeOptions('LINKFLAGS'),
192         LOGLEVELS_        = env.BuildTypeOptions('LOGLEVELS'),
193         )
194     env.Append(
195         CXXFLAGS          = [ '$CXXFLAGS_' ],
196         CPPDEFINES        = [ '$CPPDEFINES_' ],
197         LINKFLAGS         = [ '$LINKFLAGS_' ],
198         LOGLEVELS         = [ '$LOGLEVELS_' ],
199         )
200     env.SetDefault(
201         CXXFLAGS_final    = [],
202         CXXFLAGS_normal   = [],
203         CXXFLAGS_debug    = [],
204
205         CPPDEFINES_final  = [],
206         CPPDEFINES_normal = [],
207         CPPDEFINES_debug  = [],
208
209         LINKFLAGS_final   = [],
210         LINKFLAGS_normal  = [],
211         LINKFLAGS_debug   = [],
212
213         LOGLEVELS_final   = [],
214         LOGLEVELS_normal  = [],
215         LOGLEVELS_debug   = [],
216         )
217
218     # Interpret command line options
219     parseArguments(
220         env,
221         BoolVariable('final', 'Build final (optimized) build', False),
222         BoolVariable('debug', 'Link in debug symbols', False),
223         BoolVariable('profile', 'compile and link with the profiling enabled option', False),
224     )
225
226     # Set nice default options
227     env.Append(
228         CXXFLAGS_CLANG   = [ '-Wno-unneeded-internal-declaration' ], # needed for BOOST_PARAMETER_KEYWORD
229         CXXFLAGS         = [ '-Wall', '-Woverloaded-virtual',  "${profile and '-pg' or None}",
230                              '${str(CXX).split("/")[-1] == "clang++" and "$CXXFLAGS_CLANG" or None}' ],
231         CXXFLAGS_final   = [ '-O3', '-fno-threadsafe-statics', '-fno-stack-protector',
232                                "${profile and ' ' or '-ffunction-sections'}" ],
233         CXXFLAGS_normal  = [ '-O2', '-g' ],
234         CXXFLAGS_debug   = [ '-O0', '-g' ],
235
236         LINKFLAGS        = [ "${profile and '-pg' or None}" ],
237         LINKFLAGS_final  = [ "${profile and ' ' or '-Wl,--gc-sections'}" ],
238         LINKFLAGS_normal = [ '-Wl,-S' ],
239         LINKFLAGS_debug  = [ '-g' ],
240     )
241
242     env.Alias('all', '#')
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 def CleanGlob(env, targets, patterns):
260     if env.GetOption('clean'):
261         targets = SCons.Util.flatten(targets)
262         for target in targets:
263             if target in BUILD_TARGETS:
264                 patterns = map(str,SCons.Util.flatten(env.subst_list(patterns)))
265                 files = [ os.path.join(path,f)
266                           for path, subdirs, files in os.walk('.')
267                           for pattern in patterns
268                           for f in fnmatch.filter(files,pattern) ]
269                 return env.Clean(target, files)
270
271 tagfiles = None
272
273 def Doxygen(env, doxyheader=None, doxyfooter=None, doxycss=None, mydoxyfile=False, senfdoc_path=[],
274             **kw):
275     # Additional interesting keyword arguments or environment variables:
276     #    PROJECTNAME, DOCLINKS, PROJECTEMAIL, COPYRIGHT, REVISION
277
278     global senfutildir
279     global tagfiles
280     libdir=os.path.join(senfutildir, 'lib')
281
282     if tagfiles is None:
283         senfdocdir = None
284         senfdoc_path.extend(('senfdoc', '$SENFDIR', '$SENFDIR/manual',
285                              '$SENFDIR/share/doc/senf', '$SENFDIR/share/doc/libsenf-doc/html'))
286         for path in senfdoc_path:
287             path = env.Dir(path).get_path()
288             if os.path.exists(os.path.join(path, "doc/doclib.tag")):
289                 senfdocdir = path
290                 break
291         tagfiles = []
292         if senfdocdir is None:
293             if not env.GetOption('no_progress'):
294                 print "(SENF documentation not found)"
295         else:
296             for dir, dirs, files in os.walk(senfdocdir):
297                 tagfiles.extend([ os.path.join(dir,f) for f in files if f.endswith('.tag') ])
298                 if dir.endswith('/doc') :
299                     try: dirs.remove('html')
300                     except ValueError: pass
301                 for d in dirs:
302                     if d.startswith('.') : dirs.remove(d)
303
304     if env.GetOption('clean'):
305         env.Clean('doc', env.Dir('doc'))
306         if not mydoxyfile:
307             env.Clean('doc', "Doxyfile")
308
309     if not mydoxyfile:
310         # Create Doxyfile NOW
311         site_tools.Yaptu.yaptuAction("Doxyfile",
312                                      os.path.join(libdir, "Doxyfile.yap"),
313                                      env)
314
315     envvalues = [ env.Value('$PROJECTNAME'),
316                   env.Value('$DOCLINKS'),
317                   env.Value('$PROJECTEMAIL'),
318                   env.Value('$COPYRIGHT'),
319                   env.Value('$REVISION') ]
320
321     # The other files are created using dependencies
322     if doxyheader:
323         doxyheader = env.CopyToDir(env.Dir("doc"), doxyheader)
324     else:
325         doxyheader = env.Yaptu("doc/doxyheader.html", os.path.join(libdir, "doxyheader.yap"), **kw)
326         env.Depends(doxyheader, envvalues)
327     if doxyfooter:
328         doxyfooter = env.CopyToDir(env.Dir("doc"), doxyfooter)
329     else:
330         doxyfooter = env.Yaptu("doc/doxyfooter.html", os.path.join(libdir, "doxyfooter.yap"), **kw)
331         env.Depends(doxyfooter, envvalues)
332     if doxycss:
333         doxycss = env.CopyToDir(env.Dir("doc"), doxycss)
334     else:
335         doxycss    = env.CopyToDir(env.Dir("doc"), os.path.join(libdir, "doxy.css"))
336
337     doc = env.Doxygen("Doxyfile",
338                       DOXYOPTS   = [ '--html', '--tagfiles', '"$TAGFILES"' ],
339                       DOXYENV    = { 'TOPDIR'     : env.Dir('#').abspath,
340                                      'LIBDIR'     : libdir,
341                                      'REVISION'   : '$REVISION',
342                                      'tagfiles'   : '$TAGFILES',
343                                      'output_dir' : 'doc',
344                                      'html_dir'   : 'html',
345                                      'html'       : 'YES',
346                                      'DOXYGEN'    : '$DOXYGEN' },
347                       TAGFILES   = tagfiles,
348                       DOCLIBDIR  = libdir,
349                       DOXYGENCOM = "$DOCLIBDIR/doxygen.sh $DOXYOPTS $SOURCE")
350
351     env.Depends(doc, [ doxyheader, doxyfooter, doxycss ])
352
353     return doc