Fix 'scons -h'
[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 The following additional targets may be called within subdirectories, either
37 using '$ scons -u <target>'  or '$ scons <directory>/<target>:
38
39 test            Build and run unit test for this module
40 doc             Build the documentation of this module
41 valgrind        Run the unit test of this module under valgrind
42
43 You may execute targets on a remote host via ssh (if the directory layout is the
44 same) by calling
45
46     $ scons <target>@[<user>@]<host>
47
48 Some more elaborate unit tests may be enabled by setting appropritate variables
49 in the shell (unix) environment
50
51 SENF_TIMING_CRITICAL_TESTS
52                 Enables unit tests which depend on timing measurements. These
53                 unit tests should only be run on a single core and an otherwise
54                 idle system.
55
56 SENF_WLAN_TEST_INTERFACE
57                 WLAN interface to use for testing. The interface should not be
58                 actively in use.
59
60 SENF_ETH_TEST_INTERFACE
61                 Ethernet interface to use for testing. The interface should not
62                 be actively in use.
63
64 Some unit tests will only run when executed to 'root'.
65 """)
66
67 env.Replace(
68     expandLogOption        = senfutil.expandLogOption,
69     CXXFLAGS_              = env.BuildTypeOptions('CXXFLAGS'),
70     CPPDEFINES_            = env.BuildTypeOptions('CPPDEFINES'),
71     LINKFLAGS_             = env.BuildTypeOptions('LINKFLAGS'),
72 )
73 env.Append(
74     IMPORT_ENV             = [ 'PATH', 'HOME', 'SSH_*', 'SENF*', 'CCACHE_*', 'DISTCC_*' ],
75
76     CLEAN_PATTERNS         = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign*',
77                               '.sconf_temp' ],
78
79     CPPPATH                = [ '#', '$BUILDDIR',
80                                '${NEED_BOOST_EXT and "#/boost_ext" or None}' ],
81     LOCALLIBDIR            = '$BUILDDIR',
82     LIBPATH                = [ '$LOCALLIBDIR' ],
83     LIBS                   = [ '$EXTRA_LIBS' ],
84     EXTRA_LIBS             = [ 'rt' ],
85     TEST_EXTRA_LIBS        = [  ],
86     VALGRINDARGS           = [ '--num-callers=50' ],
87
88     CPP_INCLUDE_EXTENSIONS = [ '.h', '.hh', '.ih', '.mpp', '.cci', '.ct', '.cti' ],
89     CPP_EXCLUDE_EXTENSIONS = [ '.test.hh' ],
90
91     # INLINE_OPTS_DEBUG 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_', '-fno-strict-aliasing' ],
101     CXXFLAGS_final         = [ '-O3' ],
102     CXXFLAGS_normal        = [ '-O2', '-g' ],
103     CXXFLAGS_debug         = [ '-O0', '-g' ],
104
105     CPPDEFINES             = [ '$expandLogOption', '$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_final        = [ ],
112     LINKFLAGS_normal       = [ '-Wl,-S' ],
113     LINKFLAGS_debug        = [ '-g' ],
114 )
115 env.SetDefault(
116     PREFIX                 = '#/dist',
117     LIBINSTALLDIR          = '$PREFIX${syslayout and "/lib" or ""}',
118     BININSTALLDIR          = '$PREFIX${syslayout and "/bin" or ""}',
119     INCLUDEINSTALLDIR      = '$PREFIX${syslayout and "/include" or ""}',
120     CONFINSTALLDIR         = '${syslayout and "$LIBINSTALLDIR/senf" or "$PREFIX"}',
121     OBJINSTALLDIR          = '$CONFINSTALLDIR',
122     DOCINSTALLDIR          = '$PREFIX${syslayout and "/share/doc/senf" or "/manual"}',
123     SCONSINSTALLDIR        = '$CONFINSTALLDIR/site_scons',
124
125     BUILDDIR               = '${FLAVOR and "#/build/$FLAVOR" or "#"}',
126
127     LIBSENF                = "senf",
128     LCOV                   = "lcov",
129     GENHTML                = "genhtml",
130     VALGRIND               = "valgrind",
131     SCONSBIN               = env.File("#/tools/scons"),
132     SCONSARGS              = ([ '-Q', '-j$CONCURRENCY_LEVEL' ] +
133                               [ '%s=%s' % (k,v) for k,v in ARGUMENTS.iteritems() ]),
134     SCONS                  = "@$SCONSBIN $SCONSARGS",
135     CONCURRENCY_LEVEL      = env.GetOption('num_jobs') or 1,
136     TOPDIR                 = env.Dir('#').abspath,
137     LIBADDSUFFIX           = '${FLAVOR and "_$FLAVOR" or ""}',
138     OBJADDSUFFIX           = '${LIBADDSUFFIX}',
139     FLAVOR                 = '',
140 )
141
142 # Set variables from command line
143 senfutil.parseArguments(
144     env,
145     BoolVariable('final', 'Build final (optimized) build', False),
146     BoolVariable('debug', 'Link in debug symbols', False),
147     BoolVariable('syslayout', 'Install in to system layout directories (lib/, include/ etc)', False),
148     BoolVariable('sparse_tests', 'Link tests against object files and not the senf lib', False)
149 )
150
151 # Add UNIX env vars matching IMPORT_ENV patterns into the execution environment
152 senfutil.importProcessEnv(env)
153
154 # Handle 'test_changes'
155 if 'test_changes' in COMMAND_LINE_TARGETS and not env.has_key('only_tests'):
156     import SparseTestHack
157     env['only_tests'] = " ".join(x.abspath for x in SparseTestHack.findSCMChanges(env))
158
159 if env.has_key('only_tests') : env['sparse_tests'] = True
160
161 Export('env')
162
163 ###########################################################################
164 # Configure
165
166 SConscript('SConfigure')
167
168 # Only add this here, after all configure checks have run
169
170 env.Append(LIBS = '$LIBSENF$LIBADDSUFFIX',
171            EXTRA_LIBS = [ '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB',
172                           '$BOOSTFSLIB' ])
173
174 ###########################################################################
175
176 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
177 # Create it even when cleaning, to silence the doxygen builder warnings
178 if not os.path.exists("doclib/Doxyfile.local"):
179     Execute(Touch("doclib/Doxyfile.local"))
180
181 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
182    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
183     env.Execute([ "$SCONS prepare" ])
184
185 # Load SConscripts
186
187 SConscriptChdir(0)
188 SConscript("debian/SConscript")
189 SConscriptChdir(1)
190 if os.path.exists('SConscript.local') : SConscript('SConscript.local')
191 if env['sparse_tests']:
192     import SparseTestHack
193     SparseTestHack.setup(env)
194 if env.subst('$BUILDDIR') == '#':
195     SConscript("SConscript")
196 else:
197     SConscript("SConscript", variant_dir=env.subst('$BUILDDIR'), src_dir='#', duplicate=False)
198 SConscript("Examples/SConscript")
199 SConscript("HowTos/SConscript")
200 SConscript("doclib/SConscript")
201 if env['sparse_tests']:
202     verbose = 'test_changes' in COMMAND_LINE_TARGETS
203     SparseTestHack.build(env, verbose, verbose)
204
205 ###########################################################################
206 # Define build targets
207
208 #### install_all, default, all_tests, all
209 env.Install('${SCONSINSTALLDIR}', [ 'site_scons/__init__.py',
210                                     'site_scons/senfutil.py',
211                                     'site_scons/senfconf.py',
212                                     'site_scons/yaptu.py' ])
213 env.InstallDir('${SCONSINSTALLDIR}', [ 'site_scons/site_tools', 'site_scons/lib' ],
214                FILTER_SUFFIXES=[ '','.css','.pl','.py','.sh','.sty','.xml','.xsl','.yap' ])
215 env.Install('${INCLUDEINSTALLDIR}', 'boost_ext')
216 env.Install('${INCLUDEINSTALLDIR}/senf', 'senf/boost_intrusive')
217
218 env.Alias('install_all', env.FindInstalledFiles())
219 env.Alias('default', DEFAULT_TARGETS)
220 env.Alias('all_tests', env.FindAllBoostUnitTests())
221 env.Alias('test_changes', 'all_tests')
222 env.Alias('all', [ 'default', 'all_tests', 'examples', 'all_docs' ])
223
224 #### prepare
225 env.PhonyTarget('prepare', [], [])
226
227 #### valgrind
228 env.Alias('all_valgrinds')
229 if env.get('HAVE_VALGRIND'):
230     for test in env.FindAllBoostUnitTests():
231         stamp = env.Command(test[0].dir.File('.test-valgrind.stamp'),
232                             [ test[0].dir.File('.test.bin'), 'tools/valgrind.sup' ],
233                             [ """$VALGRIND --tool=memcheck
234                                           --error-exitcode=1
235                                           --suppressions=${SOURCES[1]}
236                                           $VALGRINDARGS
237                                               ${SOURCES[0]} --result_code=no $BOOSTTESTARGS
238                               """.replace("\n"," "),
239                               Touch("$TARGET") ])
240         alias = env.Command(test[0].dir.File('valgrind'), stamp, [ env.NopAction() ])
241         env.Alias('all_valgrinds', alias)
242
243 ### lcov
244 env.PhonyTarget('lcov', [], [
245         '$SCONS debug=1 BUILDDIR="#/build/lcov" CCFLAGS+="-fprofile-arcs -ftest-coverage" LIBS+="gcov" all_tests',
246         '$LCOV --follow --directory $TOPDIR/build/lcov/senf --capture --output-file /tmp/senf_lcov.info --base-directory $TOPDIR',
247         '$LCOV --output-file lcov.info --remove /tmp/senf_lcov.info "*/include/*" "*/boost/*" "*.test.*" ',
248         '$GENHTML --output-directory doc/lcov --title all_tests lcov.info',
249         'rm /tmp/senf_lcov.info' ])
250 if env.GetOption('clean'):
251     env.Clean('lcov', [ os.path.join(path,f)
252                         for path, subdirs, files in os.walk('.')
253                         for pattern in ('*.gcno', '*.gcda', '*.gcov')
254                         for f in fnmatch.filter(files,pattern) ] +
255                       [ 'lcov.info', env.Dir('doc/lcov'), env.Dir('build/lcov') ])
256
257 #### clean
258 env.Clean('all', ('.prepare-stamp', env.Dir('dist'), env.Dir('build')))
259 if env.GetOption('clean') : env.Depends('all', ('lcov', 'all_valgrinds'))
260
261 if env.GetOption('clean') and 'all' in BUILD_TARGETS:
262     env.Clean('all', [ os.path.join(path,f)
263                        for path, subdirs, files in os.walk('.')
264                        for pattern in env['CLEAN_PATTERNS']
265                        for f in fnmatch.filter(files,pattern) ])
266     # Disable writing to the deleted .sconsign file
267     import SCons.SConsign
268     SCons.SConsign.write = lambda : None
269
270 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
271     Execute(Touch(".prepare-stamp"))
272
273 ### execute targets on remote hosts
274 for target in COMMAND_LINE_TARGETS:
275     if '@' in target:
276         realtarget, host = target.split('@',1)
277         cwd=env.GetLaunchDir()
278         home=os.environ['HOME']+'/'
279         if cwd.startswith(home) : cwd = cwd[len(home):]
280         args = [ '$SCONSARGS' ]
281         if env.GetLaunchDir() != os.getcwd():
282             args.append('-u')
283         env.PhonyTarget(target, [], [ "ssh $HOST scons $SCONSARGS -C $DIR $RTARGET" ],
284                         HOST=host, RTARGET=realtarget, DIR=cwd, SCONSARGS=args)
285
286 env.PhonyTarget('clean', [], [
287         lambda **args: sys.stderr.write(
288             "=================================================================\n"
289             "'clean' is not a valid target. Instead, use\n"
290             "    $ scons -c all\n"
291             "=================================================================\n") ])