Move all build env settings from SENFSCons to SConstruct
[senf.git] / senfscons / SENFSCons.py
1 ## \file
2 # \brief SENFSCons package
3
4 ## \package senfscons.SENFSCons
5 # \brief Build helpers and utilities
6 #
7 # The SENFSCons package contains a number of build helpers and
8 # utilities which are used to simplify commmon tasks.
9 #
10 # The utitlities of this package are grouped into:
11 # <dl><dt>\ref use</dt><dd>help using complex environments and
12 # configure the construction environmen correspondingly</dd>
13 #
14 # <dt>\ref target</dt><dd>simplify building common targest and include
15 # enhanced functionality like unit-testing.</dd></dl>
16 #
17 # Additionally for external use are
18 # <dl><dt>MakeEnvironment()</dt><dd>Build construction
19 # environment</dd>
20 #
21 # <dt>GlobSources()</dt><dd>Utility to find source files</dd></dl>
22 #
23 # All other functions are for internal use only.
24
25 import os.path, glob
26 import SCons.Options, SCons.Environment, SCons.Script.SConscript, SCons.Node.FS
27 import SCons.Defaults, SCons.Action
28 from SCons.Script import *
29
30 ## \defgroup use Predefined Framework Configurators
31 #
32 # The following framework configurators are used in the top level \c
33 # SConstruct file to simplify more complex configurations.
34 #
35 # Each of the framework configurators introduces additional
36 # configuration parameters to \ref sconfig
37
38 ## \defgroup target Target Helpers
39 #
40 # To specify standard targets, the following helpers can be used. They
41 # automatically integrate several modules (like documentation,
42 # unit-testing etc).
43
44 ## \defgroup builder Builders
45 #
46 # The SENFSCons framework includes a series of builders. Each builder
47 # is defined in it's own package.
48
49 ## \brief Find normal and test C++ sources
50 #
51 # GlobSources() will return a list of all C++ source files (named
52 # "*.cc") as well as a list of all unit-test files (named "*.test.cc")
53 # in the current directory. The sources will be returned as a tuple of
54 # sources, test-sources. The target helpers all accept such a tuple as
55 # their source argument.
56 def GlobSources(env, exclude=[], subdirs=[]):
57     testSources = glob.glob("*.test.cc")
58     sources = [ x for x in glob.glob("*.cc") if x not in testSources and x not in exclude ]
59     for subdir in subdirs:
60         testSources += glob.glob(os.path.join(subdir,"*.test.cc"))
61         sources += [ x for x in glob.glob(os.path.join(subdir,"*.cc"))
62                      if x not in testSources and x not in exclude ]
63     return (sources, testSources)
64
65 def GlobIncludes(env, exclude=[], subdirs=[]):
66     includes = []
67     for d in [ '.' ] + subdirs:
68         for f in os.listdir(d):
69             ext = '.' + f.split('.',1)[-1]
70             p = os.path.join(d,f)
71             if ext in env['CPP_INCLUDE_EXTENSIONS'] \
72                and ext not in env['CPP_EXCLUDE_EXTENSIONS'] \
73                and p not in exclude:
74                 includes.append(p)
75     return includes
76
77 def Glob(env, exclude=[], subdirs=[]):
78     return ( GlobSources(env, exclude, subdirs),
79              GlobIncludes(env, exclude, subdirs) )
80
81 ## \brief Add generic standard targets for every module
82 #
83 # This target helper should be called in the top-level \c SConstruct file
84 # as well as in every module \c SConscipt file. It adds general
85 # targets. Right now, these are
86 # \li clean up \c .sconsign, \c .sconf_temp and \c config.log on
87 #   <tt>scons -c all</tt>
88 #
89 # \ingroup target
90 def StandardTargets(env):
91     env.Clean(env.Alias('all'), [ '.sconsign', '.sconf_temp', 'config.log' ])
92
93 ## \brief Add generic global targets
94 #
95 # This target helper should be called in the top-level \c SConstruct
96 # file. It adds general global targets. Right now theese are
97 # \li Make <tt>scons all</tt> build all targets.
98 #
99 # \ingroup target
100 def GlobalTargets(env):
101     env.Alias('all', [ 'default', 'all_tests', 'all_docs' ])
102
103 ## \brief Return path of a built library within $LOCALLIBDIR
104 # \internal
105 def LibPath(lib): return '${LOCALLIBDIR}/${LIBPREFIX}%s${LIBADDSUFFIX}${LIBSUFFIX}' % lib
106
107 ## \brief Add explicit test
108 #
109 # This target helper will add an explicit test. This is like a unit test but is
110 # built directly against the completed library
111 #
112 # \ingroup target
113 def Test(env, sources, LIBS = [], OBJECTS = []):
114     test = [ env.BoostUnitTests(
115         target = 'test',
116         objects = [],
117         test_sources = sources,
118         LIBS = [ '$LIBSENF$LIBADDSUFFIX' ],
119         OBJECTS = OBJECTS,
120         DEPENDS = [ env.File(LibPath(env['LIBSENF'])) ]) ]
121     compileTestSources = [ src for src in sources
122                            if 'COMPILE_CHECK' in file(src).read() ]
123     if compileTestSources:
124         test.extend(env.CompileCheck(source = compileTestSources))
125     env.Alias('all_tests', test)
126     env.Command(env.File('test'), test, [ 'true' ])
127     #env.Alias(env.File('test'), test)
128     
129
130 ## \brief Build object files
131 #
132 # This target helper will build object files from the given
133 # sources.
134 #
135 # If \a testSources are given, a unit test will be built using the <a
136 # href="http://www.boost.org/libs/test/doc/index.html">Boost.Test</a>
137 # library. \a LIBS may specify any additional library modules <em>from
138 # the same project</em> on which the test depends. Those libraries
139 # will be linked into the final test executable. The test will
140 # automatically be run if the \c test or \c all_tests targets are
141 # given.
142 #
143 # If \a sources is a 2-tuple as returned by GlobSources(), it will
144 # provide both \a sources and \a testSources.
145 #
146 # \ingroup target
147 def Objects(env, sources, testSources = None, OBJECTS = []):
148     if type(sources) == type(()):
149         testSources = sources[1]
150         sources = sources[0]
151     if type(sources) is not type([]):
152         sources = [ sources ]
153
154     objects = None
155     if sources:
156         obsources = [ source
157                       for source in sources
158                       if type(source) is type('') and not source.endswith('.o') ]
159         objects = [ source
160                     for source in sources
161                     if type(source) is not type('') or source.endswith('.o') ]
162         if obsources:
163             objects += env.Object(obsources)
164
165     if testSources:
166         test = [ env.BoostUnitTests(
167             target = 'test',
168             objects = objects,
169             test_sources = testSources,
170             LIBS = [ '$LIBSENF$LIBADDSUFFIX' ],
171             OBJECTS = OBJECTS,
172             DEPENDS = [ env.File(LibPath(env['LIBSENF'])) ]) ]
173         compileTestSources = [ src for src in testSources
174                                if 'COMPILE_CHECK' in file(src).read() ]
175         if compileTestSources:
176             test.extend(env.CompileCheck(source = compileTestSources))
177         env.Alias('all_tests', test)
178         # Hmm ... here I'd like to use an Alias instead of a file
179         # however the alias does not seem to live in the subdirectory
180         # which breaks 'scons -u test'
181         env.Command(env.File('test'), test, [ 'true' ])
182         #env.Alias(env.File('test'), test)
183
184     return objects
185
186 def InstallIncludeFiles(env, files):
187     # Hrmpf ... why do I need this in 0.97??
188     if env.GetOption('clean'):
189         return
190     target = env.Dir(env['INCLUDEINSTALLDIR'])
191     base = env.Dir('#')
192     for f in files:
193         src = env.File(f)
194         env.Alias('install_all', env.Install(target.Dir(src.dir.get_path(base)), src))
195
196 ## \brief Build documentation with doxygen
197 #
198 # \ingroup target
199 def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
200     # There is one small problem we need to solve with this builder: The Doxygen builder reads
201     # the Doxyfile and thus depends on the environment variables set by doclib/doxygen.sh. We
202     # thus have to provide all necessary definitions here manually via DOXYENV !
203
204     if type(doxyfile) is type(""):
205         doxyfile = env.File(doxyfile)
206
207     # Module name is derived from the doxyfile path
208     # Utils/Console/Doxyfile -> Utils_Console
209     module = doxyfile.dir.abspath[len(env.Dir('#').abspath)+1:].replace('/','_')
210     if not module : module = "Main"
211
212     # Rule to generate tagfile
213     # (need to exclude the 'clean' case, otherwise we'll have duplicate nodes)
214     if not env.GetOption('clean'):
215         tagfile = env.Doxygen(doxyfile,
216                               DOXYOPTS = [ '--tagfile-name', '"${MODULE}.tag"',
217                                            '--tagfile' ],
218                               DOXYENV  = { 'TOPDIR'          : env.Dir('#').abspath,
219                                            'output_dir'      : 'doc',
220                                            'html_dir'        : 'html',
221                                            'html'            : 'NO',
222                                            'generate_tagfile': 'doc/${MODULE}.tag' },
223                               MODULE   = module )
224         env.Append(ALL_TAGFILES = tagfile[0].abspath)
225         env.Depends(tagfile, env.File('#/doclib/doxygen.sh'))
226
227     # Rule to generate HTML documentation
228     doc = env.Doxygen(doxyfile,
229                       DOXYOPTS = [ '--tagfiles', '"$ALL_TAGFILES"',
230                                    '--tagfile-name', '"${MODULE}.tag"',
231                                    '--html' ],
232                       MODULE   = module,
233                       DOXYENV  = { 'TOPDIR'          : env.Dir('#').abspath,
234                                    'tagfiles'        : '${ALL_TAGFILES}',
235                                    'output_dir'      : 'doc',
236                                    'html_dir'        : 'html',
237                                    'html'            : 'YES' } )
238     env.Depends(doc, env.File('#/doclib/doxygen.sh'))
239
240     # Copy the extra_sources (the images) into the documentation directory
241     # (need to exclude the 'clean' case otherwise there are multiple ways to clean the copies)
242     if not env.GetOption('clean'):
243         if extra_sources:
244             env.Depends(doc,
245                         [ env.CopyToDir( source=source, target=doc[0].dir )
246                           for source in extra_sources ])
247
248     # Install documentation into DOCINSTALLDIR
249     l = len(env.Dir('#').abspath)
250     env.Alias('install_all',
251               env.Command('$DOCINSTALLDIR' + doc[0].dir.abspath[l:], doc[0].dir,
252                           [ SCons.Defaults.Copy('$TARGET','$SOURCE') ]))
253
254     # Useful aliases
255     env.Alias('all_docs', doc)
256     env.Clean('all_docs', doc)
257     env.Clean('all', doc)
258
259     return doc
260
261 ## \brief Build library
262 #
263 # This target helper will build the given library. The library will be
264 # called lib<i>library</i>.a as is customary on UNIX systems. \a
265 # sources, \a testSources and \a LIBS are directly forwarded to the
266 # Objects build helper.
267 #
268 # The library is added to the list of default targets.
269 #
270 #\ingroup target
271 def Lib(env, sources, testSources = None, OBJECTS = []):
272     objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
273     if objects:
274         env.Append(ALLOBJECTS = objects)
275     return objects
276
277 ## \brief Build Object from multiple sources
278 def Object(env, target, sources, testSources = None, OBJECTS = []):
279     objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
280     ob = None
281     if objects:
282         ob = env.Command(target+"${OBJADDSUFFIX}${OBJSUFFIX}", objects, "ld -r -o $TARGET $SOURCES")
283         env.Default(ob)
284         env.Alias('default', ob)
285         env.Alias('install_all', env.Install("$OBJINSTALLDIR", ob))
286     return ob
287
288 ## \brief Build executable
289 #
290 # This target helper will build the given binary.  The \a sources, \a
291 # testSources and \a LIBS arguments are forwarded to the Objects
292 # builder. The final program will be linked against all the library
293 # modules specified in \a LIBS (those are libraries which are built as
294 # part of the same proejct). To specify non-module libraries, use the
295 # construction environment parameters or the framework helpers.
296 #
297 # \ingroup target
298 def Binary(env, binary, sources, testSources = None, OBJECTS = []):
299     objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
300     program = None
301     if objects:
302         progEnv = env.Clone()
303         progEnv.Prepend(LIBS = [ '$LIBSENF$LIBADDSUFFIX' ])
304         program = progEnv.ProgramNoScan(target=binary,source=objects+OBJECTS)
305         env.Default(program)
306         env.Depends(program, [ env.File(LibPath(env['LIBSENF'])) ])
307         env.Alias('default', program)
308         env.Alias('install_all', env.Install('$BININSTALLDIR', program))
309     return program
310
311 def AllIncludesHH(env, headers):
312     headers.sort()
313     target = env.File("all_includes.hh")
314     file(target.abspath,"w").write("".join([ '#include "%s"\n' % f
315                                              for f in headers ]))
316     env.Clean('all', target)
317
318 def PhonyTarget(env, target, action, sources=[]):
319     env.AlwaysBuild(env.Alias(target, sources, env.Action(action)))