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