2 # \brief SENFSCons package
4 ## \package senfscons.SENFSCons
5 # \brief Build helpers and utilities
7 # The SENFSCons package contains a number of build helpers and
8 # utilities which are used to simplify commmon tasks.
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>
14 # <dt>\ref target</dt><dd>simplify building common targest and include
15 # enhanced functionality like unit-testing.</dd></dl>
17 # Additionally for external use are
18 # <dl><dt>MakeEnvironment()</dt><dd>Build construction
21 # <dt>GlobSources()</dt><dd>Utility to find source files</dd></dl>
23 # All other functions are for internal use only.
26 import SCons.Options, SCons.Environment, SCons.Script.SConscript, SCons.Node.FS
27 import SCons.Defaults, SCons.Action
28 from SCons.Script import *
30 ## \defgroup use Predefined Framework Configurators
32 # The following framework configurators are used in the top level \c
33 # SConstruct file to simplify more complex configurations.
35 # Each of the framework configurators introduces additional
36 # configuration parameters to \ref sconfig
38 ## \defgroup target Target Helpers
40 # To specify standard targets, the following helpers can be used. They
41 # automatically integrate several modules (like documentation,
44 ## \defgroup builder Builders
46 # The SENFSCons framework includes a series of builders. Each builder
47 # is defined in it's own package.
49 ## \brief Find normal and test C++ sources
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)
65 def GlobIncludes(env, exclude=[], subdirs=[]):
67 for d in [ '.' ] + subdirs:
68 for f in os.listdir(d):
69 ext = '.' + f.split('.',1)[-1]
71 if ext in env['CPP_INCLUDE_EXTENSIONS'] \
72 and ext not in env['CPP_EXCLUDE_EXTENSIONS'] \
77 def Glob(env, exclude=[], subdirs=[]):
78 return ( GlobSources(env, exclude, subdirs),
79 GlobIncludes(env, exclude, subdirs) )
81 ## \brief Return path of a built library within $LOCALLIBDIR
83 def LibPath(lib): return '${LOCALLIBDIR}/${LIBPREFIX}%s${LIBADDSUFFIX}${LIBSUFFIX}' % lib
85 ## \brief Add explicit test
87 # This target helper will add an explicit test. This is like a unit test but is
88 # built directly against the completed library
91 def Test(env, sources, LIBS = [], OBJECTS = []):
92 test = [ env.BoostUnitTests(
95 test_sources = sources,
96 LIBS = [ '$LIBSENF$LIBADDSUFFIX' ],
98 DEPENDS = [ env.File(LibPath(env['LIBSENF'])) ]) ]
99 compileTestSources = [ src for src in sources
100 if 'COMPILE_CHECK' in file(src).read() ]
101 if compileTestSources:
102 test.extend(env.CompileCheck(source = compileTestSources))
103 env.Alias('all_tests', test)
104 env.Command(env.File('test'), test, [ 'true' ])
105 #env.Alias(env.File('test'), test)
108 ## \brief Build object files
110 # This target helper will build object files from the given
113 # If \a testSources are given, a unit test will be built using the <a
114 # href="http://www.boost.org/libs/test/doc/index.html">Boost.Test</a>
115 # library. \a LIBS may specify any additional library modules <em>from
116 # the same project</em> on which the test depends. Those libraries
117 # will be linked into the final test executable. The test will
118 # automatically be run if the \c test or \c all_tests targets are
121 # If \a sources is a 2-tuple as returned by GlobSources(), it will
122 # provide both \a sources and \a testSources.
125 def Objects(env, sources, testSources = None, OBJECTS = []):
126 if type(sources) == type(()):
127 testSources = sources[1]
129 if type(sources) is not type([]):
130 sources = [ sources ]
135 for source in sources
136 if type(source) is type('') and not source.endswith('.o') ]
138 for source in sources
139 if type(source) is not type('') or source.endswith('.o') ]
141 objects += env.Object(obsources)
144 test = [ env.BoostUnitTests(
147 test_sources = testSources,
148 LIBS = [ '$LIBSENF$LIBADDSUFFIX' ],
150 DEPENDS = [ env.File(LibPath(env['LIBSENF'])) ]) ]
151 compileTestSources = [ src for src in testSources
152 if 'COMPILE_CHECK' in file(src).read() ]
153 if compileTestSources:
154 test.extend(env.CompileCheck(source = compileTestSources))
155 env.Alias('all_tests', test)
156 # Hmm ... here I'd like to use an Alias instead of a file
157 # however the alias does not seem to live in the subdirectory
158 # which breaks 'scons -u test'
159 env.Command(env.File('test'), test, [ 'true' ])
160 #env.Alias(env.File('test'), test)
164 def InstallIncludeFiles(env, files):
165 # Hrmpf ... why do I need this in 0.97??
166 if env.GetOption('clean'):
168 target = env.Dir(env['INCLUDEINSTALLDIR'])
172 env.Alias('install_all', env.Install(target.Dir(src.dir.get_path(base)), src))
174 ## \brief Build documentation with doxygen
177 def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
178 # There is one small problem we need to solve with this builder: The Doxygen builder reads
179 # the Doxyfile and thus depends on the environment variables set by doclib/doxygen.sh. We
180 # thus have to provide all necessary definitions here manually via DOXYENV !
182 if type(doxyfile) is type(""):
183 doxyfile = env.File(doxyfile)
185 # Module name is derived from the doxyfile path
186 # Utils/Console/Doxyfile -> Utils_Console
187 module = doxyfile.dir.abspath[len(env.Dir('#').abspath)+1:].replace('/','_')
188 if not module : module = "Main"
190 # Rule to generate tagfile
191 # (need to exclude the 'clean' case, otherwise we'll have duplicate nodes)
192 if not env.GetOption('clean'):
193 tagfile = env.Doxygen(doxyfile,
194 DOXYOPTS = [ '--tagfile-name', '"${MODULE}.tag"',
196 DOXYENV = { 'TOPDIR' : env.Dir('#').abspath,
197 'output_dir' : 'doc',
200 'generate_tagfile': 'doc/${MODULE}.tag' },
202 env.Append(ALL_TAGFILES = tagfile[0].abspath)
203 env.Depends(tagfile, env.File('#/doclib/doxygen.sh'))
205 # Rule to generate HTML documentation
206 doc = env.Doxygen(doxyfile,
207 DOXYOPTS = [ '--tagfiles', '"$ALL_TAGFILES"',
208 '--tagfile-name', '"${MODULE}.tag"',
211 DOXYENV = { 'TOPDIR' : env.Dir('#').abspath,
212 'tagfiles' : '${ALL_TAGFILES}',
213 'output_dir' : 'doc',
216 env.Depends(doc, env.File('#/doclib/doxygen.sh'))
218 # Copy the extra_sources (the images) into the documentation directory
219 # (need to exclude the 'clean' case otherwise there are multiple ways to clean the copies)
220 if not env.GetOption('clean'):
223 [ env.CopyToDir( source=source, target=doc[0].dir )
224 for source in extra_sources ])
226 # Install documentation into DOCINSTALLDIR
227 l = len(env.Dir('#').abspath)
228 env.Alias('install_all',
229 env.Command('$DOCINSTALLDIR' + doc[0].dir.abspath[l:], doc[0].dir,
230 [ SCons.Defaults.Copy('$TARGET','$SOURCE') ]))
233 env.Alias('all_docs', doc)
234 env.Clean('all_docs', doc)
235 env.Clean('all', doc)
239 ## \brief Build library
241 # This target helper will build the given library. The library will be
242 # called lib<i>library</i>.a as is customary on UNIX systems. \a
243 # sources, \a testSources and \a LIBS are directly forwarded to the
244 # Objects build helper.
246 # The library is added to the list of default targets.
249 def Lib(env, sources, testSources = None, OBJECTS = []):
250 objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
252 env.Append(ALLOBJECTS = objects)
255 ## \brief Build Object from multiple sources
256 def Object(env, target, sources, testSources = None, OBJECTS = []):
257 objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
260 ob = env.Command(target+"${OBJADDSUFFIX}${OBJSUFFIX}", objects, "ld -r -o $TARGET $SOURCES")
262 env.Alias('default', ob)
263 env.Alias('install_all', env.Install("$OBJINSTALLDIR", ob))
266 ## \brief Build executable
268 # This target helper will build the given binary. The \a sources, \a
269 # testSources and \a LIBS arguments are forwarded to the Objects
270 # builder. The final program will be linked against all the library
271 # modules specified in \a LIBS (those are libraries which are built as
272 # part of the same proejct). To specify non-module libraries, use the
273 # construction environment parameters or the framework helpers.
276 def Binary(env, binary, sources, testSources = None, OBJECTS = []):
277 objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
280 progEnv = env.Clone()
281 progEnv.Prepend(LIBS = [ '$LIBSENF$LIBADDSUFFIX' ])
282 program = progEnv.ProgramNoScan(target=binary,source=objects+OBJECTS)
284 env.Depends(program, [ env.File(LibPath(env['LIBSENF'])) ])
285 env.Alias('default', program)
286 env.Alias('install_all', env.Install('$BININSTALLDIR', program))
289 def AllIncludesHH(env, headers):
291 target = env.File("all_includes.hh")
292 file(target.abspath,"w").write("".join([ '#include "%s"\n' % f
294 env.Clean('all', target)
296 def PhonyTarget(env, target, action, sources=[]):
297 env.AlwaysBuild(env.Alias(target, sources, env.Action(action)))