8216902a39417bd0c69d74b138473889942fdd1d
[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         = [ '-Wall', '-Woverloaded-virtual',  "${profile and '-pg' or None}" ],
229         CXXFLAGS_final   = [ '-O3', '-fno-threadsafe-statics', '-fno-stack-protector',
230                                "${profile and ' ' or '-ffunction-sections'}" ],
231         CXXFLAGS_normal  = [ '-O2', '-g' ],
232         CXXFLAGS_debug   = [ '-O0', '-g' ],
233
234         LINKFLAGS        = [ "${profile and '-pg' or None}" ],
235         LINKFLAGS_final  = [ "${profile and ' ' or '-Wl,--gc-sections'}" ],
236         LINKFLAGS_normal = [ '-Wl,-S' ],
237         LINKFLAGS_debug  = [ '-g' ],
238     )
239
240     env.Alias('all', '#')
241
242
243 def Glob(env, exclude=[], subdirs=[]):
244     testSources = env.Glob("*.test.cc", strings=True)
245     sources = [ x
246                 for x in env.Glob("*.cc", strings=True)
247                 if x not in testSources and x not in exclude ]
248     for subdir in subdirs:
249         testSources += env.Glob(os.path.join(subdir,"*.test.cc"), strings=True)
250         sources += [ x
251                      for x in env.Glob(os.path.join(subdir,"*.cc"), strings=True)
252                      if x not in testSources and x not in exclude ]
253     sources.sort()
254     testSources.sort()
255     return (sources, testSources)
256
257 def CleanGlob(env, targets, patterns):
258     if env.GetOption('clean'):
259         targets = SCons.Util.flatten(targets)
260         for target in targets:
261             if target in BUILD_TARGETS:
262                 patterns = map(str,SCons.Util.flatten(env.subst_list(patterns)))
263                 files = [ os.path.join(path,f)
264                           for path, subdirs, files in os.walk('.')
265                           for pattern in patterns
266                           for f in fnmatch.filter(files,pattern) ]
267                 return env.Clean(target, files)
268
269 tagfiles = None
270
271 def Doxygen(env, doxyheader=None, doxyfooter=None, doxycss=None, mydoxyfile=False, senfdoc_path=[],
272             **kw):
273     # Additional interesting keyword arguments or environment variables:
274     #    PROJECTNAME, DOCLINKS, PROJECTEMAIL, COPYRIGHT, REVISION
275
276     global senfutildir
277     global tagfiles
278     libdir=os.path.join(senfutildir, 'lib')
279
280     if tagfiles is None:
281         senfdocdir = None
282         senfdoc_path.extend(('senfdoc', '$SENFDIR', '$SENFDIR/manual',
283                              '$SENFDIR/share/doc/senf', '$SENFDIR/share/doc/libsenf-doc/html'))
284         for path in senfdoc_path:
285             path = env.Dir(path).get_path()
286             if os.path.exists(os.path.join(path, "doc/doclib.tag")):
287                 senfdocdir = path
288                 break
289         tagfiles = []
290         if senfdocdir is None:
291             if not env.GetOption('no_progress'):
292                 print "(SENF documentation not found)"
293         else:
294             for dir, dirs, files in os.walk(senfdocdir):
295                 tagfiles.extend([ os.path.join(dir,f) for f in files if f.endswith('.tag') ])
296                 if dir.endswith('/doc') :
297                     try: dirs.remove('html')
298                     except ValueError: pass
299                 for d in dirs:
300                     if d.startswith('.') : dirs.remove(d)
301
302     if env.GetOption('clean'):
303         env.Clean('doc', env.Dir('doc'))
304         if not mydoxyfile:
305             env.Clean('doc', "Doxyfile")
306
307     if not mydoxyfile:
308         # Create Doxyfile NOW
309         site_tools.Yaptu.yaptuAction("Doxyfile",
310                                      os.path.join(libdir, "Doxyfile.yap"),
311                                      env)
312
313     envvalues = [ env.Value('$PROJECTNAME'),
314                   env.Value('$DOCLINKS'),
315                   env.Value('$PROJECTEMAIL'),
316                   env.Value('$COPYRIGHT'),
317                   env.Value('$REVISION') ]
318
319     # The other files are created using dependencies
320     if doxyheader:
321         doxyheader = env.CopyToDir(env.Dir("doc"), doxyheader)
322     else:
323         doxyheader = env.Yaptu("doc/doxyheader.html", os.path.join(libdir, "doxyheader.yap"), **kw)
324         env.Depends(doxyheader, envvalues)
325     if doxyfooter:
326         doxyfooter = env.CopyToDir(env.Dir("doc"), doxyfooter)
327     else:
328         doxyfooter = env.Yaptu("doc/doxyfooter.html", os.path.join(libdir, "doxyfooter.yap"), **kw)
329         env.Depends(doxyfooter, envvalues)
330     if doxycss:
331         doxycss = env.CopyToDir(env.Dir("doc"), doxycss)
332     else:
333         doxycss    = env.CopyToDir(env.Dir("doc"), os.path.join(libdir, "doxy.css"))
334
335     doc = env.Doxygen("Doxyfile",
336                       DOXYOPTS   = [ '--html', '--tagfiles', '"$TAGFILES"' ],
337                       DOXYENV    = { 'TOPDIR'     : env.Dir('#').abspath,
338                                      'LIBDIR'     : libdir,
339                                      'REVISION'   : '$REVISION',
340                                      'tagfiles'   : '$TAGFILES',
341                                      'output_dir' : 'doc',
342                                      'html_dir'   : 'html',
343                                      'html'       : 'YES',
344                                      'DOXYGEN'    : '$DOXYGEN' },
345                       TAGFILES   = tagfiles,
346                       DOCLIBDIR  = libdir,
347                       DOXYGENCOM = "$DOCLIBDIR/doxygen.sh $DOXYOPTS $SOURCE")
348
349     env.Depends(doc, [ doxyheader, doxyfooter, doxycss ])
350
351     return doc