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