Add some docs and diagnostics to test_changes build target
[senf.git] / SConstruct
1 # -*- python -*-
2
3 import sys, os.path, fnmatch
4 import SENFSCons, senfutil
5
6 ###########################################################################
7 # Load utilities and setup libraries and configure build
8
9 env = Environment()
10
11 env.Decider('MD5-timestamp')
12 env.EnsureSConsVersion(1,2)
13
14 # Load all the local SCons tools
15 senfutil.loadTools(env)
16
17 env.Help("""
18 Additional top-level build targets:
19
20 prepare        Create all target files not part of the repository
21 default        Build all default targets (like calling scons with no arguments)
22 examples       Build all examples
23 all_tests      Build and run unit tests for all modules
24 test_changes   Build tests only for files with local changes (queries svn or git)
25 all_docs       Build documentation for all modules
26 all            Build everything
27 install_all    Install SENF into $$PREFIX
28 deb            Build debian source and binary package
29 debsrc         Build debian source package
30 debbin         Build debian binary package
31 linklint       Check links of doxygen documentation with 'linklint'
32 fixlinks       Fix broken links in doxygen documentation
33 all_valgrinds  Run all tests under valgrind/memcheck
34 lcov           Generate test coverage output in doc/lcov and lcov.info
35
36 You may execute targets on a remote host (if the directory layout is the same)
37 by calling
38
39     scons <target>@[<user>@]<host>
40 """)
41
42 env.Append(
43    ENV                    = { 'PATH' : os.environ.get('PATH'), 
44                               'HOME' : os.environ.get('HOME'),
45                               'SSH_AGENT_PID': os.environ.get('SSH_AGENT_PID'),
46                               'SSH_AUTH_SOCK': os.environ.get('SSH_AUTH_SOCK') },
47    CLEAN_PATTERNS         = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign*' ],
48
49    BUILDDIR               = '${FLAVOR and "#/build/$FLAVOR" or "#"}',
50    CPPPATH                = [ '$BUILDDIR', '#' ],
51    LOCALLIBDIR            = '$BUILDDIR',
52    LIBPATH                = [ '$LOCALLIBDIR' ],
53    LIBS                   = [ '$LIBSENF$LIBADDSUFFIX', '$EXTRA_LIBS' ],
54    EXTRA_LIBS             = [ 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', 
55                               '$BOOSTFSLIB' ], 
56    TEST_EXTRA_LIBS        = [  ],
57    VALGRINDARGS           = [ '--num-callers=50' ],
58
59    PREFIX                 = '#/dist',
60    LIBINSTALLDIR          = '$PREFIX${syslayout and "/lib" or ""}',
61    BININSTALLDIR          = '$PREFIX${syslayout and "/bin" or ""}',
62    INCLUDEINSTALLDIR      = '$PREFIX${syslayout and "/include" or ""}',
63    CONFINSTALLDIR         = '${syslayout and "$LIBINSTALLDIR/senf" or "$PREFIX"}',
64    OBJINSTALLDIR          = '$CONFINSTALLDIR',
65    DOCINSTALLDIR          = '$PREFIX${syslayout and "/share/doc/senf" or "/manual"}',
66    SCONSINSTALLDIR        = '$CONFINSTALLDIR/site_scons',
67
68    CPP_INCLUDE_EXTENSIONS = [ '.h', '.hh', '.ih', '.mpp', '.cci', '.ct', '.cti' ],
69    CPP_EXCLUDE_EXTENSIONS = [ '.test.hh' ],
70
71    # These options are insane. Only useful for inline debugging. Need at least 1G free RAM
72    INLINE_OPTS_DEBUG      = [ '-finline-limit=20000', '-fvisibility-inlines-hidden', 
73                               '-fno-inline-functions', '-Winline' 
74                               '--param','large-function-growth=10000',
75                               '--param', 'large-function-insns=10000', 
76                               '--param','inline-unit-growth=10000' ],
77    INLINE_OPTS_NORMAL     = [ '-finline-limit=5000' ],
78    INLINE_OPTS            = [ '$INLINE_OPTS_NORMAL' ],
79    CXXFLAGS               = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long', '$INLINE_OPTS',
80                               '$CXXFLAGS_' ],
81    CXXFLAGS_              = senfutil.BuildTypeOptions('CXXFLAGS'),
82    CXXFLAGS_final         = [ '-O3' ],
83    CXXFLAGS_normal        = [ '-O0', '-g' ],
84    CXXFLAGS_debug         = [ '$CXXFLAGS_normal' ],
85
86    CPPDEFINES             = [ '$expandLogOption', '$CPPDEFINES_' ],
87    expandLogOption        = senfutil.expandLogOption,
88    CPPDEFINES_            = senfutil.BuildTypeOptions('CPPDEFINES'),
89    CPPDEFINES_final       = [ ],
90    CPPDEFINES_normal      = [ 'SENF_DEBUG' ],
91    CPPDEFINES_debug       = [ '$CPPDEFINES_normal' ],
92
93    LINKFLAGS              = [ '-rdynamic', '$LINKFLAGS_' ],
94    LINKFLAGS_             = senfutil.BuildTypeOptions('LINKFLAGS'),
95    LINKFLAGS_final        = [ ],
96    LINKFLAGS_normal       = [ '-Wl,-S' ],
97    LINKFLAGS_debug        = [ '-g' ],
98 )
99
100 env.SetDefault(
101     LIBSENF           = "senf",
102     LCOV              = "lcov",
103     GENHTML           = "genhtml",
104     SCONSBIN          = env.File("#/tools/scons"),
105     SCONSARGS          = [ '-Q', '-j$CONCURRENCY_LEVEL', 'debug=$debug', 'final=$final' ],
106     SCONS             = "@$SCONSBIN $SCONSARGS",
107     CONCURRENCY_LEVEL = env.GetOption('num_jobs') or 1,
108     TOPDIR            = env.Dir('#').abspath,
109     LIBADDSUFFIX      = '${FLAVOR and "_$FLAVOR" or ""}',
110     OBJADDSUFFIX      = '${LIBADDSUFFIX}',
111     FLAVOR            = '',
112 )
113
114 # Set variables from command line
115 senfutil.parseArguments(
116     env,
117     BoolVariable('final', 'Build final (optimized) build', False),
118     BoolVariable('debug', 'Link in debug symbols', False),
119     BoolVariable('syslayout', 'Install in to system layout directories (lib/, include/ etc)', False),
120     BoolVariable('sparse_tests', 'Link tests against object files and not the senf lib', False)
121 )
122
123 if 'test_changes' in COMMAND_LINE_TARGETS and not env.has_key('only_tests'):
124     import SparseTestHack
125     env['only_tests'] = " ".join(x.abspath for x in SparseTestHack.findSCMChanges(env))
126
127 if env.has_key('only_tests') : env['sparse_tests'] = True
128 Export('env')
129
130 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
131 # Create it even when cleaning, to silence the doxygen builder warnings
132 if not os.path.exists("doclib/Doxyfile.local"):
133     Execute(Touch("doclib/Doxyfile.local"))
134
135 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
136    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
137     env.Execute([ "$SCONS prepare" ])
138
139 # Load SConscripts
140
141 SConscriptChdir(0)
142 SConscript("debian/SConscript")
143 SConscriptChdir(1)
144 if os.path.exists('SConscript.local') : SConscript('SConscript.local')
145 if env['sparse_tests']:
146     import SparseTestHack
147     SparseTestHack.setup(env)
148 if env.subst('$BUILDDIR') == '#':
149     SConscript("SConscript")
150 else:
151     SConscript("SConscript", variant_dir=env.subst('$BUILDDIR'), src_dir='#', duplicate=False)
152 SConscript("Examples/SConscript")
153 SConscript("HowTos/SConscript")
154 SConscript("doclib/SConscript")
155 if env['sparse_tests']:
156     verbose = 'test_changes' in COMMAND_LINE_TARGETS
157     SparseTestHack.build(env, verbose, verbose)
158
159 ###########################################################################
160 # Define build targets
161
162 #### install_all, default, all_tests, all
163 env.Install('${SCONSINSTALLDIR}', [ 'site_scons/__init__.py',
164                                     'site_scons/senfutil.py',
165                                     'site_scons/yaptu.py' ])
166 env.InstallDir('${SCONSINSTALLDIR}', [ 'site_scons/site_tools', 'site_scons/lib' ],
167                FILTER_SUFFIXES=[ '','.css','.pl','.py','.sh','.sty','.xml','.xsl','.yap' ])
168 env.Install('${INCLUDEINSTALLDIR}', 'boost')
169
170 env.Alias('install_all', env.FindInstalledFiles())
171 env.Alias('default', DEFAULT_TARGETS)
172 env.Alias('all_tests', env.FindAllBoostUnitTests())
173 env.Alias('test_changes', 'all_tests')
174 env.Alias('all', [ 'default', 'all_tests', 'examples', 'all_docs' ])
175
176 #### prepare
177 env.PhonyTarget('prepare', [], [])
178
179 #### valgrind
180 for test in env.FindAllBoostUnitTests():
181     stamp = env.Command(test[0].dir.File('.test-valgrind.stamp'), 
182                         [ test[0].dir.File('.test.bin'), 'tools/valgrind.sup' ],
183                         [ """valgrind --tool=memcheck 
184                                       --error-exitcode=99 
185                                       --suppressions=${SOURCES[1]}
186                                       $VALGRINDARGS
187                                           ${SOURCES[0]} $BOOSTTESTARGS;
188                              [ $$? -ne 99 ] || exit 1""".replace("\n"," "),
189                           Touch("$TARGET") ])
190     alias = env.Command(test[0].dir.File('valgrind'), stamp, [ env.NopAction() ])
191     env.Alias('all_valgrinds', alias)
192
193 ### lcov
194 env.PhonyTarget('lcov', [], [
195         '$SCONS debug=1 BUILDDIR="#/build/lcov" CCFLAGS+="-fprofile-arcs -ftest-coverage" LIBS+="gcov" all_tests',
196         '$LCOV --follow --directory $TOPDIR/build/lcov/senf --capture --output-file /tmp/senf_lcov.info --base-directory $TOPDIR',
197         '$LCOV --output-file lcov.info --remove /tmp/senf_lcov.info "*/include/*" "*/boost/*" "*.test.*" ',
198         '$GENHTML --output-directory doc/lcov --title all_tests lcov.info',
199         'rm /tmp/senf_lcov.info' ])
200 if env.GetOption('clean'): 
201     env.Clean('lcov', [ os.path.join(path,f)
202                         for path, subdirs, files in os.walk('.')
203                         for pattern in ('*.gcno', '*.gcda', '*.gcov')
204                         for f in fnmatch.filter(files,pattern) ] + 
205                       [ 'lcov.info', env.Dir('doc/lcov'), env.Dir('build/lcov') ])
206     
207 #### clean
208 env.Clean('all', ('.prepare-stamp', env.Dir('dist'), env.Dir('build')))
209 if env.GetOption('clean') : env.Depends('all', ('lcov', 'all_valgrinds'))
210
211 if env.GetOption('clean') and 'all' in BUILD_TARGETS:
212     env.Clean('all', [ os.path.join(path,f)
213                        for path, subdirs, files in os.walk('.')
214                        for pattern in env['CLEAN_PATTERNS']
215                        for f in fnmatch.filter(files,pattern) ])
216     # Disable writing to the deleted .sconsign file
217     import SCons.SConsign
218     SCons.SConsign.write = lambda : None
219
220 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
221     Execute(Touch(".prepare-stamp"))
222
223 ### execute targets on remote hosts
224 for target in COMMAND_LINE_TARGETS:
225     if '@' in target:
226         realtarget, host = target.split('@',1)
227         cwd=env.GetLaunchDir()
228         home=os.environ['HOME']+'/'
229         if cwd.startswith(home) : cwd = cwd[len(home):]
230         args = [ '$SCONSARGS' ]
231         if env.GetLaunchDir() != os.getcwd():
232             args.append('-u')
233         env.PhonyTarget(target, [], [ "ssh $HOST scons $SCONSARGS -C $DIR $RTARGET" ],
234                         HOST=host, RTARGET=realtarget, DIR=cwd, SCONSARGS=args)