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