Completely rework documentation build
[senf.git] / SConstruct
1 # -*- python -*-
2
3 import sys, glob, os.path, datetime, pwd, time, fnmatch, string
4 sys.path.append(Dir('#/senfscons').abspath)
5 sys.path.append(Dir('#/doclib').abspath)
6 import SENFSCons
7
8 ###########################################################################
9
10 # This hack is needed for SCons V 0.96.1 compatibility. In current SCons versions
11 # we can just use 'env.AlwaysBuild(env.Alias(target), [], action)'
12 def PhonyTarget(env, target, action, sources=[]):
13     env.AlwaysBuild(env.Command(target + '.phony', [ 'SConstruct' ] + sources, env.Action(action)))
14     env.Alias(target, target + '.phony')
15
16 def updateRevision(target, source, env):
17     rev = env['ENV']['REVISION'][1:]
18     if ':' in rev:
19         print
20         print "Working copy not clean. Run 'svn update'"
21         print
22         return 1
23     if 'm' in rev and not ARGUMENTS.get('force_deb'):
24         print
25         print "Working copy contains local changes. Commit first"
26         print
27         return 1
28     if 's' in rev:
29         rev = rev[:-1]
30     if 'm' in rev:
31         rev = rev[:-1]
32     url = None
33     for line in os.popen("svn info"):
34         elts=line.split(':',1)
35         if elts[0] == 'URL':
36             url = elts[1].strip()
37     version = None
38     if '/tags/' in url:
39         version = url.rsplit('/',1)[-1].split('_',1)[0]
40         if version[0] not in string.digits:
41             version = None
42     if version is None:
43         version = '1:0r%s' % rev
44     changelog = file('debian/changelog.template').read() % {
45         'version': version,
46         'rev': rev,
47         'user': pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0].strip(),
48         'date': time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) }
49     file('debian/changelog','w').write(changelog)
50
51 def nonemptyFile(f):
52     try: return os.stat(f).st_size > 0
53     except OSError: return False
54
55 def checkLocalConf(target, source, env):
56     if [ True for f in env['LOCAL_CONFIG_FILES'] if nonemptyFile(f) ]:
57         print
58         print "You have made local modifications to one of the following local configuration"
59         print "files:"
60         for f in env['LOCAL_CONFIG_FILES']:
61             print "    ",f
62         print
63         print "Building a debian package would remove those files."
64         print
65         print "To continue, remove the offending file(s) and try again. Alternatively,"
66         print "build a source package using 'scons debsrc' and may then build debian"
67         print "binary packages from this source-package without disrupting your local"
68         print "configuration."
69         print
70         return 1
71
72 ###########################################################################
73 # Load utilities and setup libraries and configure build
74
75 SENFSCons.UseBoost()
76 SENFSCons.UseSTLPort()
77 env = SENFSCons.MakeEnvironment()
78
79 env.Help("""
80 Additional top-level build targets:
81
82 prepare      Create all source files not part of the repository
83 all_tests    Build and run unit tests for all modules
84 all_docs     Build documentation for all modules
85 all          Build everything
86 install_all  Install SENF into $PREFIX
87 deb          Build debian source and binary package
88 debsrc       Build debian source package
89 debbin       Build debian binary package
90 linklint     Check links of doxygen documentation with 'linklint'
91 fixlinks     Fix broken links in doxygen documentation
92 valgrind     Run all tests under valgrind/memcheck
93 """)
94
95 if os.environ.get('debian_build'):
96     rev = os.popen("dpkg-parsechangelog | awk '/^Version:/{print $2}'").read().strip()
97 else:
98     rev = 'r' + os.popen("svnversion").read().strip().lower()
99
100 logname = os.environ.get('LOGNAME')
101 if not logname:
102     logname = pwd.getpwuid(os.getuid()).pw_name
103
104 def dpkgIgnoredFilesOpts(target, source, env, for_signature):
105     return [ '-I%s' % (('/' in f) and (os.path.split(os.getcwd())[1])+f or f)
106              for f in env.subst('$DPKG_IGNORED_FILES').split() ]
107
108 # Options used to debug inlining:
109 #
110 # INLINE_OPTS = [ '-finline-limit=20000', '--param','large-function-growth=10000',
111 #                 '--param', 'large-function-insns=10000', '--param','inline-unit-growth=10000',
112 #                 '-fvisibility-inlines-hidden', '-fno-inline-functions', '-Winline' ]
113 #
114 # BEWARE: You need lots of ram to compile with these settings (approx 1G)
115 #
116
117 INLINE_OPTS = [ '-finline-limit=5000' ]
118
119 env.Append(
120    CPPPATH = [ '#/include' ],
121    CXXFLAGS = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long' ] + INLINE_OPTS,
122    LIBS = [ 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB',
123             '$BOOSTFSLIB' ],
124    TEST_EXTRA_LIBS = [ ],
125    ENV = { 'TODAY' : str(datetime.date.today()),
126            'REVISION' : rev,
127            'LOGNAME' : logname, # needed by the debian build scripts
128            'CONCURRENCY_LEVEL' : env.GetOption('num_jobs') or "1",
129            'SCONS' : 1,
130            'PATH' : os.environ.get('PATH'),
131            'TEXINPUTS' : os.environ.get('TEXINPUTS',env.Dir('#/doclib').abspath + ':'),
132            'DOXYGEN' : os.environ.get('DOXYGEN', 'doxygen'),
133          },
134    LOCAL_CONFIG_FILES = [ '/Doxyfile.local', '/SConfig', '/local_config.hh' ],
135    DPKG_IGNORED_FILES = [ '$LOCAL_CONFIG_FILES', '.svn', '/_templates' ],
136    DPKG_IGNORED_FILES_OPTS = dpkgIgnoredFilesOpts,
137    CLEAN_PATTERNS = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign', '.sconsign.dblite' ],
138    BUILDPACKAGE_COMMAND = "dpkg-buildpackage -us -uc -rfakeroot $DPKG_IGNORED_FILES_OPTS",
139    TOP_INCLUDES = [ 'Packets', 'PPI', 'Scheduler', 'Socket', 'Utils', 'Console',
140                     'config.hh', 'local_config.hh' ],
141    ALL_TAGFILES = []
142 )
143
144 env.Replace(
145    DOXYGENCOM = "doclib/doxygen.sh $DOXYOPTS $SOURCE",
146 )
147
148 def parseLogOption(value):
149     stream, area, level = ( x.strip() for x in value.strip().split('|') )
150     if stream  : stream = ''.join('(%s)' % x for x in stream.split('::') )
151     else       : stream = '(_)'
152     if area : area = ''.join( '(%s)' % x for x in area.split('::') )
153     else    : area = '(_)'
154     return '(( %s,%s,%s ))' % (stream,area,level)
155
156 def expandLogOption(target, source, env, for_signature):
157     return ' '.join( parseLogOption(x) for x in env.subst('$LOGLEVELS').split() )
158
159 if env.subst('$LOGLEVELS'):
160     env.Append( expandLogOption=expandLogOption )
161     env.Append( CPPDEFINES = { 'SENF_LOG_CONF': '$expandLogOption' } )
162
163 env.SetDefault(
164        LIBSENF = "senf"
165 )
166
167 Export('env')
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("Doxyfile.local"):
172     Execute(Touch("Doxyfile.local"))
173
174 # Create local_config.h
175 if not env.GetOption('clean') and not os.path.exists("local_config.hh"):
176     Execute(Touch("local_config.hh"))
177
178 ###########################################################################
179 # Define build targets
180
181 # Before defining any targets, check wether this is the first build in
182 # pristine directory tree. If so, call 'scons prepare' so the dependencies
183 # created later are correct
184
185 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
186    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
187     env.Execute([ "scons prepare" ])
188
189 env.Clean('all', '.prepare-stamp')
190
191 SConscript(glob.glob("*/SConscript"))
192
193 SENFSCons.StandardTargets(env)
194 SENFSCons.GlobalTargets(env)
195 env.Depends( SENFSCons.Doxygen(env), env.Value(rev) )
196
197 SENFSCons.InstallIncludeFiles(env, [ 'config.hh' ])
198
199 # Build combined library 'libsenf'
200 libsenf = env.Library(env.subst("$LIBSENF$LIBADDSUFFIX"), env['ALLOBJECTS'])
201 env.Default(libsenf)
202 env.Clean('all', libsenf)
203 env.Alias('default', libsenf)
204
205 env.Alias('install_all', env.Install('$LIBINSTALLDIR', libsenf))
206
207 if env.GetOption('clean'):
208     env.Clean('all', [ os.path.join(path,f)
209                        for path, subdirs, files in os.walk('.')
210                        for pattern in env['CLEAN_PATTERNS']
211                        for f in fnmatch.filter(files,pattern) ])
212
213 PhonyTarget(env, 'deb', [
214     checkLocalConf,
215     updateRevision,
216     "$BUILDPACKAGE_COMMAND",
217     "fakeroot ./debian/rules debclean"
218 ])
219
220 PhonyTarget(env, 'debsrc', [
221     updateRevision,
222     "$BUILDPACKAGE_COMMAND -S",
223 ])
224
225 PhonyTarget(env, 'debbin', [
226     checkLocalConf,
227     updateRevision,
228     "$BUILDPACKAGE_COMMAND -b",
229     "fakeroot ./debian/rules debclean"
230 ])
231
232 PhonyTarget(env, 'linklint', [
233     'rm -rf linklint',
234     'linklint -doc linklint -limit 99999999 `find -type d -name html -printf "/%P/@ "`',
235     '[ ! -r linklint/errorX.html ] || python doclib/linklint_addnames.py <linklint/errorX.html >linklint/errorX.html.new',
236     '[ ! -r linklint/errorX.html.new ] || mv linklint/errorX.html.new linklint/errorX.html',
237     '[ ! -r linklint/errorAX.html ] || python doclib/linklint_addnames.py <linklint/errorAX.html >linklint/errorAX.html.new',
238     '[ ! -r linklint/errorAX.html.new ] || mv linklint/errorAX.html.new linklint/errorAX.html',
239     'echo -e "\\nLokal link check results: linklint/index.html\\nRemote link check results: linklint/urlindex.html\\n"',
240 ])
241
242 PhonyTarget(env, 'fixlinks', [
243     'python doclib/fix-links.py -v -s .svn -s linklint -s debian linklint/errorX.txt linklint/errorAX.txt',
244 ])
245
246 PhonyTarget(env, 'prepare', [ 'true' ])
247
248 PhonyTarget(env, 'valgrind', [
249     'find -name .test.bin | while read test; do echo; echo "Running $$test"; echo; valgrind --tool=memcheck --error-exitcode=99 --suppressions=valgrind.sup $$test $BOOSTTESTARGS; [ $$? -ne 99 ] || exit 1; done'
250     ], [ 'all_tests' ])
251
252 env.Clean('all', env.Dir('linklint'))
253
254 env.Clean('all','.prepare-stamp')
255 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
256     Execute(Touch(".prepare-stamp"))