9b4065a7a4d3f5a69c475734f0858c32ab69e01f
[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         )
174
175     try:
176         path = env.File('$BUNDLEDIR/senf${LIBADDSUFFIX}.conf').abspath
177         env.MergeFlags(file(path).read())
178     except IOError:
179         # Really should never happen since detect_senf looks for this file ...
180         pass
181
182 ###########################################################################
183 # Helpers
184
185 def DefaultOptions(env):
186     env.Replace(
187         expandLogOption   = expandLogOption,
188         CXXFLAGS_         = env.BuildTypeOptions('CXXFLAGS'),
189         CPPDEFINES_       = env.BuildTypeOptions('CPPDEFINES'),
190         LINKFLAGS_        = env.BuildTypeOptions('LINKFLAGS'),
191         LOGLEVELS_        = env.BuildTypeOptions('LOGLEVELS'),
192         )
193     env.Append(
194         CXXFLAGS          = [ '$CXXFLAGS_' ],
195         CPPDEFINES        = [ '$CPPDEFINES_' ],
196         LINKFLAGS         = [ '$LINKFLAGS_' ],
197         LOGLEVELS         = [ '$LOGLEVELS_' ],
198         )
199     env.SetDefault(
200         CXXFLAGS_final    = [],
201         CXXFLAGS_normal   = [],
202         CXXFLAGS_debug    = [],
203
204         CPPDEFINES_final  = [],
205         CPPDEFINES_normal = [],
206         CPPDEFINES_debug  = [],
207
208         LINKFLAGS_final   = [],
209         LINKFLAGS_normal  = [],
210         LINKFLAGS_debug   = [],
211
212         LOGLEVELS_final   = [],
213         LOGLEVELS_normal  = [],
214         LOGLEVELS_debug   = [],
215         )
216
217     # Interpret command line options
218     parseArguments(
219         env,
220         BoolVariable('final', 'Build final (optimized) build', False),
221         BoolVariable('debug', 'Link in debug symbols', False),
222         BoolVariable('profile', 'compile and link with the profiling enabled option', False),
223     )
224
225     # Set nice default options
226     env.Append(
227         CXXFLAGS         = [ '-Wall', '-Woverloaded-virtual',  "${profile and '-pg' or None}" ],
228         CXXFLAGS_final   = [ '-O3', '-fno-threadsafe-statics', '-fno-stack-protector',
229                              '-ffunction-sections' ],
230         CXXFLAGS_normal  = [ '-O2', '-g' ],
231         CXXFLAGS_debug   = [ '-O0', '-g' ],
232
233         LINKFLAGS        = [ "${profile and '-pg' or None}" ],
234         LINKFLAGS_final  = [ '-Wl,--gc-sections' ],
235         LINKFLAGS_normal = [ '-Wl,-S' ],
236         LINKFLAGS_debug  = [ '-g' ],
237     )
238
239     env.Alias('all', '#')
240
241
242 def Glob(env, exclude=[], subdirs=[]):
243     testSources = env.Glob("*.test.cc", strings=True)
244     sources = [ x
245                 for x in env.Glob("*.cc", strings=True)
246                 if x not in testSources and x not in exclude ]
247     for subdir in subdirs:
248         testSources += env.Glob(os.path.join(subdir,"*.test.cc"), strings=True)
249         sources += [ x
250                      for x in env.Glob(os.path.join(subdir,"*.cc"), strings=True)
251                      if x not in testSources and x not in exclude ]
252     sources.sort()
253     testSources.sort()
254     return (sources, testSources)
255
256 def CleanGlob(env, targets, patterns):
257     if env.GetOption('clean'):
258         targets = SCons.Util.flatten(targets)
259         for target in targets:
260             if target in BUILD_TARGETS:
261                 patterns = map(str,SCons.Util.flatten(env.subst_list(patterns)))
262                 files = [ os.path.join(path,f)
263                           for path, subdirs, files in os.walk('.')
264                           for pattern in patterns
265                           for f in fnmatch.filter(files,pattern) ]
266                 return env.Clean(target, files)
267
268 tagfiles = None
269
270 def Doxygen(env, doxyheader=None, doxyfooter=None, doxycss=None, mydoxyfile=False, senfdoc_path=[],
271             **kw):
272     # Additional interesting keyword arguments or environment variables:
273     #    PROJECTNAME, DOCLINKS, PROJECTEMAIL, COPYRIGHT, REVISION
274
275     global senfutildir
276     global tagfiles
277     libdir=os.path.join(senfutildir, 'lib')
278
279     if tagfiles is None:
280         senfdocdir = None
281         senfdoc_path.extend(('senfdoc', '$SENFDIR', '$SENFDIR/manual',
282                              '$SENFDIR/share/doc/senf', '$SENFDIR/share/doc/libsenf-doc/html'))
283         for path in senfdoc_path:
284             path = env.Dir(path).get_path()
285             if os.path.exists(os.path.join(path, "doc/doclib.tag")):
286                 senfdocdir = path
287                 break
288         tagfiles = []
289         if senfdocdir is None:
290             if not env.GetOption('no_progress'):
291                 print "(SENF documentation not found)"
292         else:
293             for dir, dirs, files in os.walk(senfdocdir):
294                 tagfiles.extend([ os.path.join(dir,f) for f in files if f.endswith('.tag') ])
295                 if dir.endswith('/doc') :
296                     try: dirs.remove('html')
297                     except ValueError: pass
298                 for d in dirs:
299                     if d.startswith('.') : dirs.remove(d)
300
301     if env.GetOption('clean'):
302         env.Clean('doc', env.Dir('doc'))
303         if not mydoxyfile:
304             env.Clean('doc', "Doxyfile")
305
306     if not mydoxyfile:
307         # Create Doxyfile NOW
308         site_tools.Yaptu.yaptuAction("Doxyfile",
309                                      os.path.join(libdir, "Doxyfile.yap"),
310                                      env)
311
312     envvalues = [ env.Value('$PROJECTNAME'),
313                   env.Value('$DOCLINKS'),
314                   env.Value('$PROJECTEMAIL'),
315                   env.Value('$COPYRIGHT'),
316                   env.Value('$REVISION') ]
317
318     # The other files are created using dependencies
319     if doxyheader:
320         doxyheader = env.CopyToDir(env.Dir("doc"), doxyheader)
321     else:
322         doxyheader = env.Yaptu("doc/doxyheader.html", os.path.join(libdir, "doxyheader.yap"), **kw)
323         env.Depends(doxyheader, envvalues)
324     if doxyfooter:
325         doxyfooter = env.CopyToDir(env.Dir("doc"), doxyfooter)
326     else:
327         doxyfooter = env.Yaptu("doc/doxyfooter.html", os.path.join(libdir, "doxyfooter.yap"), **kw)
328         env.Depends(doxyfooter, envvalues)
329     if doxycss:
330         doxycss = env.CopyToDir(env.Dir("doc"), doxycss)
331     else:
332         doxycss    = env.CopyToDir(env.Dir("doc"), os.path.join(libdir, "doxy.css"))
333
334     doc = env.Doxygen("Doxyfile",
335                       DOXYOPTS   = [ '--html', '--tagfiles', '"$TAGFILES"' ],
336                       DOXYENV    = { 'TOPDIR'     : env.Dir('#').abspath,
337                                      'LIBDIR'     : libdir,
338                                      'REVISION'   : '$REVISION',
339                                      'tagfiles'   : '$TAGFILES',
340                                      'output_dir' : 'doc',
341                                      'html_dir'   : 'html',
342                                      'html'       : 'YES',
343                                      'DOXYGEN'    : '$DOXYGEN' },
344                       TAGFILES   = tagfiles,
345                       DOCLIBDIR  = libdir,
346                       DOXYGENCOM = "$DOCLIBDIR/doxygen.sh $DOXYOPTS $SOURCE")
347
348     env.Depends(doc, [ doxyheader, doxyfooter, doxycss ])
349
350     return doc