Add unit test env-var doc to 'scons -h'
[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 Some more elaborate unit tests may be enabled by setting appropritate variables 
42 in the shell (unix) environment
43
44 SENF_TIMING_CRITICAL_TESTS
45                 Enables unit tests which depend on timing measurements. These
46                 unit tests should only be run on a single core and an otherwise
47                 idle system.
48
49 SENF_WLAN_TEST_INTERFACE
50                 WLAN interface to use for testing. The interface should not be
51                 actively in use.
52
53 SENF_ETH_TEST_INTERFACE
54                 Ethernet interface to use for testing. The interface should not
55                 be actively in use.
56
57 Some unit tests will only run when executed to 'root'.
58 """)
59
60 env.Append(
61    ENV                    = { 'PATH' : os.environ.get('PATH'), 
62                               'HOME' : os.environ.get('HOME'),
63                               'SSH_AGENT_PID': os.environ.get('SSH_AGENT_PID'),
64                               'SSH_AUTH_SOCK': os.environ.get('SSH_AUTH_SOCK') },
65    CLEAN_PATTERNS         = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign*' ],
66
67    BUILDDIR               = '${FLAVOR and "#/build/$FLAVOR" or "#"}',
68    CPPPATH                = [ '$BUILDDIR', '#' ],
69    LOCALLIBDIR            = '$BUILDDIR',
70    LIBPATH                = [ '$LOCALLIBDIR' ],
71    LIBS                   = [ '$LIBSENF$LIBADDSUFFIX', '$EXTRA_LIBS' ],
72    EXTRA_LIBS             = [ 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', 
73                               '$BOOSTFSLIB' ], 
74    TEST_EXTRA_LIBS        = [  ],
75    VALGRINDARGS           = [ '--num-callers=50' ],
76
77    PREFIX                 = '#/dist',
78    LIBINSTALLDIR          = '$PREFIX${syslayout and "/lib" or ""}',
79    BININSTALLDIR          = '$PREFIX${syslayout and "/bin" or ""}',
80    INCLUDEINSTALLDIR      = '$PREFIX${syslayout and "/include" or ""}',
81    CONFINSTALLDIR         = '${syslayout and "$LIBINSTALLDIR/senf" or "$PREFIX"}',
82    OBJINSTALLDIR          = '$CONFINSTALLDIR',
83    DOCINSTALLDIR          = '$PREFIX${syslayout and "/share/doc/senf" or "/manual"}',
84    SCONSINSTALLDIR        = '$CONFINSTALLDIR/site_scons',
85
86    CPP_INCLUDE_EXTENSIONS = [ '.h', '.hh', '.ih', '.mpp', '.cci', '.ct', '.cti' ],
87    CPP_EXCLUDE_EXTENSIONS = [ '.test.hh' ],
88
89    # These options are insane. Only useful for inline debugging. Need at least 1G free RAM
90    INLINE_OPTS_DEBUG      = [ '-finline-limit=20000', '-fvisibility-inlines-hidden', 
91                               '-fno-inline-functions', '-Winline' 
92                               '--param','large-function-growth=10000',
93                               '--param', 'large-function-insns=10000', 
94                               '--param','inline-unit-growth=10000' ],
95    INLINE_OPTS_NORMAL     = [ '-finline-limit=5000' ],
96    INLINE_OPTS            = [ '$INLINE_OPTS_NORMAL' ],
97    CXXFLAGS               = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long', '$INLINE_OPTS',
98                               '$CXXFLAGS_' ],
99    CXXFLAGS_              = senfutil.BuildTypeOptions('CXXFLAGS'),
100    CXXFLAGS_final         = [ '-O3' ],
101    CXXFLAGS_normal        = [ '-O0', '-g' ],
102    CXXFLAGS_debug         = [ '$CXXFLAGS_normal' ],
103
104    CPPDEFINES             = [ '$expandLogOption', '$CPPDEFINES_' ],
105    expandLogOption        = senfutil.expandLogOption,
106    CPPDEFINES_            = senfutil.BuildTypeOptions('CPPDEFINES'),
107    CPPDEFINES_final       = [ ],
108    CPPDEFINES_normal      = [ 'SENF_DEBUG' ],
109    CPPDEFINES_debug       = [ '$CPPDEFINES_normal' ],
110
111    LINKFLAGS              = [ '-rdynamic', '$LINKFLAGS_' ],
112    LINKFLAGS_             = senfutil.BuildTypeOptions('LINKFLAGS'),
113    LINKFLAGS_final        = [ ],
114    LINKFLAGS_normal       = [ '-Wl,-S' ],
115    LINKFLAGS_debug        = [ '-g' ],
116 )
117
118 # Add all UNIX env vars starting with 'SENF' to the execution environment
119 env.Append( ENV = dict(((k,v) for k,v in os.environ.iteritems() if k.startswith("SENF"))) )
120
121 env.SetDefault(
122     LIBSENF           = "senf",
123     LCOV              = "lcov",
124     GENHTML           = "genhtml",
125     SCONSBIN          = env.File("#/tools/scons"),
126     SCONSARGS         = [ '-Q', '-j$CONCURRENCY_LEVEL', 'debug=$debug', 'final=$final' ] + \
127         [ '%s=%s' % (k,v) for k,v in ARGUMENTS.iteritems() ],
128     SCONS             = "@$SCONSBIN $SCONSARGS",
129     CONCURRENCY_LEVEL = env.GetOption('num_jobs') or 1,
130     TOPDIR            = env.Dir('#').abspath,
131     LIBADDSUFFIX      = '${FLAVOR and "_$FLAVOR" or ""}',
132     OBJADDSUFFIX      = '${LIBADDSUFFIX}',
133     FLAVOR            = '',
134 )
135
136 # Set variables from command line
137 senfutil.parseArguments(
138     env,
139     BoolVariable('final', 'Build final (optimized) build', False),
140     BoolVariable('debug', 'Link in debug symbols', False),
141     BoolVariable('syslayout', 'Install in to system layout directories (lib/, include/ etc)', False),
142     BoolVariable('sparse_tests', 'Link tests against object files and not the senf lib', False)
143 )
144
145 if 'test_changes' in COMMAND_LINE_TARGETS and not env.has_key('only_tests'):
146     import SparseTestHack
147     env['only_tests'] = " ".join(x.abspath for x in SparseTestHack.findSCMChanges(env))
148
149 if env.has_key('only_tests') : env['sparse_tests'] = True
150 Export('env')
151
152 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
153 # Create it even when cleaning, to silence the doxygen builder warnings
154 if not os.path.exists("doclib/Doxyfile.local"):
155     Execute(Touch("doclib/Doxyfile.local"))
156
157 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
158    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
159     env.Execute([ "$SCONS prepare" ])
160
161 # Load SConscripts
162
163 SConscriptChdir(0)
164 SConscript("debian/SConscript")
165 SConscriptChdir(1)
166 if os.path.exists('SConscript.local') : SConscript('SConscript.local')
167 if env['sparse_tests']:
168     import SparseTestHack
169     SparseTestHack.setup(env)
170 if env.subst('$BUILDDIR') == '#':
171     SConscript("SConscript")
172 else:
173     SConscript("SConscript", variant_dir=env.subst('$BUILDDIR'), src_dir='#', duplicate=False)
174 SConscript("Examples/SConscript")
175 SConscript("HowTos/SConscript")
176 SConscript("doclib/SConscript")
177 if env['sparse_tests']:
178     verbose = 'test_changes' in COMMAND_LINE_TARGETS
179     SparseTestHack.build(env, verbose, verbose)
180
181 ###########################################################################
182 # Define build targets
183
184 #### install_all, default, all_tests, all
185 env.Install('${SCONSINSTALLDIR}', [ 'site_scons/__init__.py',
186                                     'site_scons/senfutil.py',
187                                     'site_scons/yaptu.py' ])
188 env.InstallDir('${SCONSINSTALLDIR}', [ 'site_scons/site_tools', 'site_scons/lib' ],
189                FILTER_SUFFIXES=[ '','.css','.pl','.py','.sh','.sty','.xml','.xsl','.yap' ])
190 env.Install('${INCLUDEINSTALLDIR}', 'boost')
191
192 env.Alias('install_all', env.FindInstalledFiles())
193 env.Alias('default', DEFAULT_TARGETS)
194 env.Alias('all_tests', env.FindAllBoostUnitTests())
195 env.Alias('test_changes', 'all_tests')
196 env.Alias('all', [ 'default', 'all_tests', 'examples', 'all_docs' ])
197
198 #### prepare
199 env.PhonyTarget('prepare', [], [])
200
201 #### valgrind
202 for test in env.FindAllBoostUnitTests():
203     stamp = env.Command(test[0].dir.File('.test-valgrind.stamp'), 
204                         [ test[0].dir.File('.test.bin'), 'tools/valgrind.sup' ],
205                         [ """valgrind --tool=memcheck 
206                                       --error-exitcode=99 
207                                       --suppressions=${SOURCES[1]}
208                                       $VALGRINDARGS
209                                           ${SOURCES[0]} $BOOSTTESTARGS;
210                              [ $$? -ne 99 ] || exit 1""".replace("\n"," "),
211                           Touch("$TARGET") ])
212     alias = env.Command(test[0].dir.File('valgrind'), stamp, [ env.NopAction() ])
213     env.Alias('all_valgrinds', alias)
214
215 ### lcov
216 env.PhonyTarget('lcov', [], [
217         '$SCONS debug=1 BUILDDIR="#/build/lcov" CCFLAGS+="-fprofile-arcs -ftest-coverage" LIBS+="gcov" all_tests',
218         '$LCOV --follow --directory $TOPDIR/build/lcov/senf --capture --output-file /tmp/senf_lcov.info --base-directory $TOPDIR',
219         '$LCOV --output-file lcov.info --remove /tmp/senf_lcov.info "*/include/*" "*/boost/*" "*.test.*" ',
220         '$GENHTML --output-directory doc/lcov --title all_tests lcov.info',
221         'rm /tmp/senf_lcov.info' ])
222 if env.GetOption('clean'): 
223     env.Clean('lcov', [ os.path.join(path,f)
224                         for path, subdirs, files in os.walk('.')
225                         for pattern in ('*.gcno', '*.gcda', '*.gcov')
226                         for f in fnmatch.filter(files,pattern) ] + 
227                       [ 'lcov.info', env.Dir('doc/lcov'), env.Dir('build/lcov') ])
228     
229 #### clean
230 env.Clean('all', ('.prepare-stamp', env.Dir('dist'), env.Dir('build')))
231 if env.GetOption('clean') : env.Depends('all', ('lcov', 'all_valgrinds'))
232
233 if env.GetOption('clean') and 'all' in BUILD_TARGETS:
234     env.Clean('all', [ os.path.join(path,f)
235                        for path, subdirs, files in os.walk('.')
236                        for pattern in env['CLEAN_PATTERNS']
237                        for f in fnmatch.filter(files,pattern) ])
238     # Disable writing to the deleted .sconsign file
239     import SCons.SConsign
240     SCons.SConsign.write = lambda : None
241
242 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
243     Execute(Touch(".prepare-stamp"))
244
245 ### execute targets on remote hosts
246 for target in COMMAND_LINE_TARGETS:
247     if '@' in target:
248         realtarget, host = target.split('@',1)
249         cwd=env.GetLaunchDir()
250         home=os.environ['HOME']+'/'
251         if cwd.startswith(home) : cwd = cwd[len(home):]
252         args = [ '$SCONSARGS' ]
253         if env.GetLaunchDir() != os.getcwd():
254             args.append('-u')
255         env.PhonyTarget(target, [], [ "ssh $HOST scons $SCONSARGS -C $DIR $RTARGET" ],
256                         HOST=host, RTARGET=realtarget, DIR=cwd, SCONSARGS=args)