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