Remove SENFSCons.StandardTargets and SENFSCons.GlobalTargets
[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 Return path of a built library within $LOCALLIBDIR
82 # \internal
83 def LibPath(lib): return '${LOCALLIBDIR}/${LIBPREFIX}%s${LIBADDSUFFIX}${LIBSUFFIX}' % lib
84
85 ## \brief Add explicit test
86 #
87 # This target helper will add an explicit test. This is like a unit test but is
88 # built directly against the completed library
89 #
90 # \ingroup target
91 def Test(env, sources, LIBS = [], OBJECTS = []):
92     test = [ env.BoostUnitTests(
93         target = 'test',
94         objects = [],
95         test_sources = sources,
96         LIBS = [ '$LIBSENF$LIBADDSUFFIX' ],
97         OBJECTS = OBJECTS,
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)
106     
107
108 ## \brief Build object files
109 #
110 # This target helper will build object files from the given
111 # sources.
112 #
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
119 # given.
120 #
121 # If \a sources is a 2-tuple as returned by GlobSources(), it will
122 # provide both \a sources and \a testSources.
123 #
124 # \ingroup target
125 def Objects(env, sources, testSources = None, OBJECTS = []):
126     if type(sources) == type(()):
127         testSources = sources[1]
128         sources = sources[0]
129     if type(sources) is not type([]):
130         sources = [ sources ]
131
132     objects = None
133     if sources:
134         obsources = [ source
135                       for source in sources
136                       if type(source) is type('') and not source.endswith('.o') ]
137         objects = [ source
138                     for source in sources
139                     if type(source) is not type('') or source.endswith('.o') ]
140         if obsources:
141             objects += env.Object(obsources)
142
143     if testSources:
144         test = [ env.BoostUnitTests(
145             target = 'test',
146             objects = objects,
147             test_sources = testSources,
148             LIBS = [ '$LIBSENF$LIBADDSUFFIX' ],
149             OBJECTS = OBJECTS,
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)
161
162     return objects
163
164 def InstallIncludeFiles(env, files):
165     # Hrmpf ... why do I need this in 0.97??
166     if env.GetOption('clean'):
167         return
168     target = env.Dir(env['INCLUDEINSTALLDIR'])
169     base = env.Dir('#')
170     for f in files:
171         src = env.File(f)
172         env.Alias('install_all', env.Install(target.Dir(src.dir.get_path(base)), src))
173
174 ## \brief Build documentation with doxygen
175 #
176 # \ingroup target
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 !
181
182     if type(doxyfile) is type(""):
183         doxyfile = env.File(doxyfile)
184
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"
189
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"',
195                                            '--tagfile' ],
196                               DOXYENV  = { 'TOPDIR'          : env.Dir('#').abspath,
197                                            'output_dir'      : 'doc',
198                                            'html_dir'        : 'html',
199                                            'html'            : 'NO',
200                                            'generate_tagfile': 'doc/${MODULE}.tag' },
201                               MODULE   = module )
202         env.Append(ALL_TAGFILES = tagfile[0].abspath)
203         env.Depends(tagfile, env.File('#/doclib/doxygen.sh'))
204
205     # Rule to generate HTML documentation
206     doc = env.Doxygen(doxyfile,
207                       DOXYOPTS = [ '--tagfiles', '"$ALL_TAGFILES"',
208                                    '--tagfile-name', '"${MODULE}.tag"',
209                                    '--html' ],
210                       MODULE   = module,
211                       DOXYENV  = { 'TOPDIR'          : env.Dir('#').abspath,
212                                    'tagfiles'        : '${ALL_TAGFILES}',
213                                    'output_dir'      : 'doc',
214                                    'html_dir'        : 'html',
215                                    'html'            : 'YES' } )
216     env.Depends(doc, env.File('#/doclib/doxygen.sh'))
217
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'):
221         if extra_sources:
222             env.Depends(doc,
223                         [ env.CopyToDir( source=source, target=doc[0].dir )
224                           for source in extra_sources ])
225
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') ]))
231
232     # Useful aliases
233     env.Alias('all_docs', doc)
234     env.Clean('all_docs', doc)
235     env.Clean('all', doc)
236
237     return doc
238
239 ## \brief Build library
240 #
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.
245 #
246 # The library is added to the list of default targets.
247 #
248 #\ingroup target
249 def Lib(env, sources, testSources = None, OBJECTS = []):
250     objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
251     if objects:
252         env.Append(ALLOBJECTS = objects)
253     return objects
254
255 ## \brief Build Object from multiple sources
256 def Object(env, target, sources, testSources = None, OBJECTS = []):
257     objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
258     ob = None
259     if objects:
260         ob = env.Command(target+"${OBJADDSUFFIX}${OBJSUFFIX}", objects, "ld -r -o $TARGET $SOURCES")
261         env.Default(ob)
262         env.Alias('default', ob)
263         env.Alias('install_all', env.Install("$OBJINSTALLDIR", ob))
264     return ob
265
266 ## \brief Build executable
267 #
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.
274 #
275 # \ingroup target
276 def Binary(env, binary, sources, testSources = None, OBJECTS = []):
277     objects = Objects(env,sources,testSources,OBJECTS=OBJECTS)
278     program = None
279     if objects:
280         progEnv = env.Clone()
281         progEnv.Prepend(LIBS = [ '$LIBSENF$LIBADDSUFFIX' ])
282         program = progEnv.ProgramNoScan(target=binary,source=objects+OBJECTS)
283         env.Default(program)
284         env.Depends(program, [ env.File(LibPath(env['LIBSENF'])) ])
285         env.Alias('default', program)
286         env.Alias('install_all', env.Install('$BININSTALLDIR', program))
287     return program
288
289 def AllIncludesHH(env, headers):
290     headers.sort()
291     target = env.File("all_includes.hh")
292     file(target.abspath,"w").write("".join([ '#include "%s"\n' % f
293                                              for f in headers ]))
294     env.Clean('all', target)
295
296 def PhonyTarget(env, target, action, sources=[]):
297     env.AlwaysBuild(env.Alias(target, sources, env.Action(action)))