Rework configure stuff
[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 def customChecks(conf):
180     conf.env.Replace(
181         HAVE_VALGRIND  = conf.CheckValgrind() and conf.CheckValgrindWildcards()
182         )
183
184 senfutil.Configure(env, customChecks)
185
186 # Only add this here, after all configure checks have run
187
188 env.Append(LIBS = '$LIBSENF$LIBADDSUFFIX',
189            EXTRA_LIBS = [ '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', 
190                           '$BOOSTFSLIB' ])
191
192 ###########################################################################
193
194 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
195 # Create it even when cleaning, to silence the doxygen builder warnings
196 if not os.path.exists("doclib/Doxyfile.local"):
197     Execute(Touch("doclib/Doxyfile.local"))
198
199 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
200    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
201     env.Execute([ "$SCONS prepare" ])
202
203 # Load SConscripts
204
205 SConscriptChdir(0)
206 SConscript("debian/SConscript")
207 SConscriptChdir(1)
208 if os.path.exists('SConscript.local') : SConscript('SConscript.local')
209 if env['sparse_tests']:
210     import SparseTestHack
211     SparseTestHack.setup(env)
212 if env.subst('$BUILDDIR') == '#':
213     SConscript("SConscript")
214 else:
215     SConscript("SConscript", variant_dir=env.subst('$BUILDDIR'), src_dir='#', duplicate=False)
216 SConscript("Examples/SConscript")
217 SConscript("HowTos/SConscript")
218 SConscript("doclib/SConscript")
219 if env['sparse_tests']:
220     verbose = 'test_changes' in COMMAND_LINE_TARGETS
221     SparseTestHack.build(env, verbose, verbose)
222
223 ###########################################################################
224 # Define build targets
225
226 #### install_all, default, all_tests, all
227 env.Install('${SCONSINSTALLDIR}', [ 'site_scons/__init__.py',
228                                     'site_scons/senfutil.py',
229                                     'site_scons/senfconf.py',
230                                     'site_scons/yaptu.py' ])
231 env.InstallDir('${SCONSINSTALLDIR}', [ 'site_scons/site_tools', 'site_scons/lib' ],
232                FILTER_SUFFIXES=[ '','.css','.pl','.py','.sh','.sty','.xml','.xsl','.yap' ])
233 env.Install('${INCLUDEINSTALLDIR}', 'boost_ext')
234 env.Install('${INCLUDEINSTALLDIR}/senf', 'senf/boost_intrusive')
235
236 env.Alias('install_all', env.FindInstalledFiles())
237 env.Alias('default', DEFAULT_TARGETS)
238 env.Alias('all_tests', env.FindAllBoostUnitTests())
239 env.Alias('test_changes', 'all_tests')
240 env.Alias('all', [ 'default', 'all_tests', 'examples', 'all_docs' ])
241
242 #### prepare
243 env.PhonyTarget('prepare', [], [])
244
245 #### valgrind
246 env.Alias('all_valgrinds')
247 if env['HAVE_VALGRIND']:
248     for test in env.FindAllBoostUnitTests():
249         stamp = env.Command(test[0].dir.File('.test-valgrind.stamp'), 
250                             [ test[0].dir.File('.test.bin'), 'tools/valgrind.sup' ],
251                             [ """$VALGRIND --tool=memcheck 
252                                           --error-exitcode=1
253                                           --suppressions=${SOURCES[1]}
254                                           $VALGRINDARGS
255                                               ${SOURCES[0]} --result_code=no $BOOSTTESTARGS
256                               """.replace("\n"," "),
257                               Touch("$TARGET") ])
258         alias = env.Command(test[0].dir.File('valgrind'), stamp, [ env.NopAction() ])
259         env.Alias('all_valgrinds', alias)
260
261 ### lcov
262 env.PhonyTarget('lcov', [], [
263         '$SCONS debug=1 BUILDDIR="#/build/lcov" CCFLAGS+="-fprofile-arcs -ftest-coverage" LIBS+="gcov" all_tests',
264         '$LCOV --follow --directory $TOPDIR/build/lcov/senf --capture --output-file /tmp/senf_lcov.info --base-directory $TOPDIR',
265         '$LCOV --output-file lcov.info --remove /tmp/senf_lcov.info "*/include/*" "*/boost/*" "*.test.*" ',
266         '$GENHTML --output-directory doc/lcov --title all_tests lcov.info',
267         'rm /tmp/senf_lcov.info' ])
268 if env.GetOption('clean'): 
269     env.Clean('lcov', [ os.path.join(path,f)
270                         for path, subdirs, files in os.walk('.')
271                         for pattern in ('*.gcno', '*.gcda', '*.gcov')
272                         for f in fnmatch.filter(files,pattern) ] + 
273                       [ 'lcov.info', env.Dir('doc/lcov'), env.Dir('build/lcov') ])
274     
275 #### clean
276 env.Clean('all', ('.prepare-stamp', env.Dir('dist'), env.Dir('build')))
277 if env.GetOption('clean') : env.Depends('all', ('lcov', 'all_valgrinds'))
278
279 if env.GetOption('clean') and 'all' in BUILD_TARGETS:
280     env.Clean('all', [ os.path.join(path,f)
281                        for path, subdirs, files in os.walk('.')
282                        for pattern in env['CLEAN_PATTERNS']
283                        for f in fnmatch.filter(files,pattern) ])
284     # Disable writing to the deleted .sconsign file
285     import SCons.SConsign
286     SCons.SConsign.write = lambda : None
287
288 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
289     Execute(Touch(".prepare-stamp"))
290
291 ### execute targets on remote hosts
292 for target in COMMAND_LINE_TARGETS:
293     if '@' in target:
294         realtarget, host = target.split('@',1)
295         cwd=env.GetLaunchDir()
296         home=os.environ['HOME']+'/'
297         if cwd.startswith(home) : cwd = cwd[len(home):]
298         args = [ '$SCONSARGS' ]
299         if env.GetLaunchDir() != os.getcwd():
300             args.append('-u')
301         env.PhonyTarget(target, [], [ "ssh $HOST scons $SCONSARGS -C $DIR $RTARGET" ],
302                         HOST=host, RTARGET=realtarget, DIR=cwd, SCONSARGS=args)