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