Implement test_changes build target
[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 all_docs       Build documentation for all modules
25 all            Build everything
26 install_all    Install SENF into $$PREFIX
27 deb            Build debian source and binary package
28 debsrc         Build debian source package
29 debbin         Build debian binary package
30 linklint       Check links of doxygen documentation with 'linklint'
31 fixlinks       Fix broken links in doxygen documentation
32 all_valgrinds  Run all tests under valgrind/memcheck
33 lcov           Generate test coverage output in doc/lcov and lcov.info
34
35 You may execute targets on a remote host (if the directory layout is the same)
36 by calling
37
38     scons <target>@[<user>@]<host>
39 """)
40
41 env.Append(
42    ENV                    = { 'PATH' : os.environ.get('PATH'), 
43                               'HOME' : os.environ.get('HOME'),
44                               'SSH_AGENT_PID': os.environ.get('SSH_AGENT_PID'),
45                               'SSH_AUTH_SOCK': os.environ.get('SSH_AUTH_SOCK') },
46    CLEAN_PATTERNS         = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign*' ],
47
48    BUILDDIR               = '${FLAVOR and "#/build/$FLAVOR" or "#"}',
49    CPPPATH                = [ '$BUILDDIR', '#' ],
50    LOCALLIBDIR            = '$BUILDDIR',
51    LIBPATH                = [ '$LOCALLIBDIR' ],
52    LIBS                   = [ '$LIBSENF$LIBADDSUFFIX', '$EXTRA_LIBS' ],
53    EXTRA_LIBS             = [ 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', 
54                               '$BOOSTFSLIB' ], 
55    TEST_EXTRA_LIBS        = [  ],
56    VALGRINDARGS           = [ '--num-callers=50' ],
57
58    PREFIX                 = '#/dist',
59    LIBINSTALLDIR          = '$PREFIX${syslayout and "/lib" or ""}',
60    BININSTALLDIR          = '$PREFIX${syslayout and "/bin" or ""}',
61    INCLUDEINSTALLDIR      = '$PREFIX${syslayout and "/include" or ""}',
62    CONFINSTALLDIR         = '${syslayout and "$LIBINSTALLDIR/senf" or "$PREFIX"}',
63    OBJINSTALLDIR          = '$CONFINSTALLDIR',
64    DOCINSTALLDIR          = '$PREFIX${syslayout and "/share/doc/senf" or "/manual"}',
65    SCONSINSTALLDIR        = '$CONFINSTALLDIR/site_scons',
66
67    CPP_INCLUDE_EXTENSIONS = [ '.h', '.hh', '.ih', '.mpp', '.cci', '.ct', '.cti' ],
68    CPP_EXCLUDE_EXTENSIONS = [ '.test.hh' ],
69
70    # These options are insane. Only useful for inline debugging. Need at least 1G free RAM
71    INLINE_OPTS_DEBUG      = [ '-finline-limit=20000', '-fvisibility-inlines-hidden', 
72                               '-fno-inline-functions', '-Winline' 
73                               '--param','large-function-growth=10000',
74                               '--param', 'large-function-insns=10000', 
75                               '--param','inline-unit-growth=10000' ],
76    INLINE_OPTS_NORMAL     = [ '-finline-limit=5000' ],
77    INLINE_OPTS            = [ '$INLINE_OPTS_NORMAL' ],
78    CXXFLAGS               = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long', '$INLINE_OPTS',
79                               '$CXXFLAGS_' ],
80    CXXFLAGS_              = senfutil.BuildTypeOptions('CXXFLAGS'),
81    CXXFLAGS_final         = [ '-O3' ],
82    CXXFLAGS_normal        = [ '-O0', '-g' ],
83    CXXFLAGS_debug         = [ '$CXXFLAGS_normal' ],
84
85    CPPDEFINES             = [ '$expandLogOption', '$CPPDEFINES_' ],
86    expandLogOption        = senfutil.expandLogOption,
87    CPPDEFINES_            = senfutil.BuildTypeOptions('CPPDEFINES'),
88    CPPDEFINES_final       = [ ],
89    CPPDEFINES_normal      = [ 'SENF_DEBUG' ],
90    CPPDEFINES_debug       = [ '$CPPDEFINES_normal' ],
91
92    LINKFLAGS              = [ '-rdynamic', '$LINKFLAGS_' ],
93    LINKFLAGS_             = senfutil.BuildTypeOptions('LINKFLAGS'),
94    LINKFLAGS_final        = [ ],
95    LINKFLAGS_normal       = [ '-Wl,-S' ],
96    LINKFLAGS_debug        = [ '-g' ],
97 )
98
99 env.SetDefault(
100     LIBSENF           = "senf",
101     LCOV              = "lcov",
102     GENHTML           = "genhtml",
103     SCONSBIN          = env.File("#/tools/scons"),
104     SCONSARGS          = [ '-Q', '-j$CONCURRENCY_LEVEL', 'debug=$debug', 'final=$final' ],
105     SCONS             = "@$SCONSBIN $SCONSARGS",
106     CONCURRENCY_LEVEL = env.GetOption('num_jobs') or 1,
107     TOPDIR            = env.Dir('#').abspath,
108     LIBADDSUFFIX      = '${FLAVOR and "_$FLAVOR" or ""}',
109     OBJADDSUFFIX      = '${LIBADDSUFFIX}',
110     FLAVOR            = '',
111 )
112
113 # Set variables from command line
114 senfutil.parseArguments(
115     env,
116     BoolVariable('final', 'Build final (optimized) build', False),
117     BoolVariable('debug', 'Link in debug symbols', False),
118     BoolVariable('syslayout', 'Install in to system layout directories (lib/, include/ etc)', False),
119     BoolVariable('sparse_tests', 'Link tests against object files and not the senf lib', False)
120 )
121
122 if 'test_changes' in COMMAND_LINE_TARGETS and not env.has_key('only_tests'):
123     if os.popen("svnversion").read().strip() == "exported":
124         env['only_tests'] = " ".join(os.popen("git ls-files --modified").read().strip().split("\n"))
125     else:
126         env['only_tests'] = " ".join(l[7:] 
127                                      for l in os.popen("svn status").read().rstrip().split("\n")
128                                      if l[0] == 'M')
129
130 if env.has_key('only_tests') : env['sparse_tests'] = True
131 Export('env')
132
133 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
134 # Create it even when cleaning, to silence the doxygen builder warnings
135 if not os.path.exists("doclib/Doxyfile.local"):
136     Execute(Touch("doclib/Doxyfile.local"))
137
138 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
139    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
140     env.Execute([ "$SCONS prepare" ])
141
142 # Load SConscripts
143
144 SConscriptChdir(0)
145 SConscript("debian/SConscript")
146 SConscriptChdir(1)
147 if os.path.exists('SConscript.local') : SConscript('SConscript.local')
148 if env['sparse_tests']:
149     import SparseTestHack
150     SparseTestHack.setup(env)
151 if env.subst('$BUILDDIR') == '#':
152     SConscript("SConscript")
153 else:
154     SConscript("SConscript", variant_dir=env.subst('$BUILDDIR'), src_dir='#', duplicate=False)
155 SConscript("Examples/SConscript")
156 SConscript("HowTos/SConscript")
157 SConscript("doclib/SConscript")
158 if env['sparse_tests']:
159     SparseTestHack.build(env, 'test_changes' in COMMAND_LINE_TARGETS)
160
161 ###########################################################################
162 # Define build targets
163
164 #### install_all, default, all_tests, all
165 env.Install('${SCONSINSTALLDIR}', [ 'site_scons/__init__.py',
166                                     'site_scons/senfutil.py',
167                                     'site_scons/yaptu.py' ])
168 env.InstallDir('${SCONSINSTALLDIR}', [ 'site_scons/site_tools', 'site_scons/lib' ],
169                FILTER_SUFFIXES=[ '','.css','.pl','.py','.sh','.sty','.xml','.xsl','.yap' ])
170 env.Install('${INCLUDEINSTALLDIR}', 'boost')
171
172 env.Alias('install_all', env.FindInstalledFiles())
173 env.Alias('default', DEFAULT_TARGETS)
174 env.Alias('all_tests', env.FindAllBoostUnitTests())
175 env.Alias('test_changes', 'all_tests')
176 env.Alias('all', [ 'default', 'all_tests', 'examples', 'all_docs' ])
177
178 #### prepare
179 env.PhonyTarget('prepare', [], [])
180
181 #### valgrind
182 for test in env.FindAllBoostUnitTests():
183     stamp = env.Command(test[0].dir.File('.test-valgrind.stamp'), 
184                         [ test[0].dir.File('.test.bin'), 'tools/valgrind.sup' ],
185                         [ """valgrind --tool=memcheck 
186                                       --error-exitcode=99 
187                                       --suppressions=${SOURCES[1]}
188                                       $VALGRINDARGS
189                                           ${SOURCES[0]} $BOOSTTESTARGS;
190                              [ $$? -ne 99 ] || exit 1""".replace("\n"," "),
191                           Touch("$TARGET") ])
192     alias = env.Command(test[0].dir.File('valgrind'), stamp, [ env.NopAction() ])
193     env.Alias('all_valgrinds', alias)
194
195 ### lcov
196 env.PhonyTarget('lcov', [], [
197         '$SCONS debug=1 BUILDDIR="#/build/lcov" CCFLAGS+="-fprofile-arcs -ftest-coverage" LIBS+="gcov" all_tests',
198         '$LCOV --follow --directory $TOPDIR/build/lcov/senf --capture --output-file /tmp/senf_lcov.info --base-directory $TOPDIR',
199         '$LCOV --output-file lcov.info --remove /tmp/senf_lcov.info "*/include/*" "*/boost/*" "*.test.*" ',
200         '$GENHTML --output-directory doc/lcov --title all_tests lcov.info',
201         'rm /tmp/senf_lcov.info' ])
202 if env.GetOption('clean'): 
203     env.Clean('lcov', [ os.path.join(path,f)
204                         for path, subdirs, files in os.walk('.')
205                         for pattern in ('*.gcno', '*.gcda', '*.gcov')
206                         for f in fnmatch.filter(files,pattern) ] + 
207                       [ 'lcov.info', env.Dir('doc/lcov'), env.Dir('build/lcov') ])
208     
209 #### clean
210 env.Clean('all', ('.prepare-stamp', env.Dir('dist'), env.Dir('build')))
211 if env.GetOption('clean') : env.Depends('all', ('lcov', 'all_valgrinds'))
212
213 if env.GetOption('clean') and 'all' in BUILD_TARGETS:
214     env.Clean('all', [ os.path.join(path,f)
215                        for path, subdirs, files in os.walk('.')
216                        for pattern in env['CLEAN_PATTERNS']
217                        for f in fnmatch.filter(files,pattern) ])
218     # Disable writing to the deleted .sconsign file
219     import SCons.SConsign
220     SCons.SConsign.write = lambda : None
221
222 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
223     Execute(Touch(".prepare-stamp"))
224
225 ### execute targets on remote hosts
226 for target in COMMAND_LINE_TARGETS:
227     if '@' in target:
228         realtarget, host = target.split('@',1)
229         cwd=env.GetLaunchDir()
230         home=os.environ['HOME']+'/'
231         if cwd.startswith(home) : cwd = cwd[len(home):]
232         args = [ '$SCONSARGS' ]
233         if env.GetLaunchDir() != os.getcwd():
234             args.append('-u')
235         env.PhonyTarget(target, [], [ "ssh $HOST scons $SCONSARGS -C $DIR $RTARGET" ],
236                         HOST=host, RTARGET=realtarget, DIR=cwd, SCONSARGS=args)