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