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