Add libboost-signals library to build setup
[senf.git] / SConstruct
1 # -*- python -*-
2
3 import sys, glob, os.path, datetime, pwd, time, fnmatch, string
4 sys.path.append('senfscons')
5 import SENFSCons
6
7 ###########################################################################
8
9 # This hack is needed for SCons V 0.96.1 compatibility. In current SCons versions
10 # we can just use 'env.AlwaysBuild(env.Alias(target), [], action)'
11 def PhonyTarget(env, target, action, sources=[]):
12     env.AlwaysBuild(env.Command(target + '.phony', [ 'SConstruct' ] + sources, env.Action(action)))
13     env.Alias(target, target + '.phony')
14
15 def updateRevision(target, source, env):
16     rev = env['ENV']['REVISION'][1:]
17     if ':' in rev:
18         print
19         print "Working copy not clean. Run 'svn update'"
20         print
21         return 1
22     if 'm' in rev and not ARGUMENTS.get('force_deb'):
23         print
24         print "Working copy contains local changes. Commit first"
25         print
26         return 1
27     if 's' in rev:
28         rev = rev[:-1]
29     if 'm' in rev:
30         rev = rev[:-1]
31     url = None
32     for line in os.popen("svn info"):
33         elts=line.split(':',1)
34         if elts[0] == 'URL':
35             url = elts[1].strip()
36     version = None
37     if '/tags/' in url:
38         version = url.rsplit('/',1)[-1].split('_',1)[0]
39         if version[0] not in string.digits:
40             version = None
41     if version is None:
42         version = '1:0r%s' % rev
43     changelog = file('debian/changelog.template').read() % {
44         'version': version,
45         'rev': rev,
46         'user': pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0].strip(),
47         'date': time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) }
48     file('debian/changelog','w').write(changelog)
49
50 def nonemptyFile(f):
51     try: return os.stat(f).st_size > 0
52     except OSError: return False
53
54 def checkLocalConf(target, source, env):
55     if [ True for f in env['LOCAL_CONFIG_FILES'] if nonemptyFile(f) ]:
56         print
57         print "You have made local modifications to one of the following local configuration"
58         print "files:"
59         for f in env['LOCAL_CONFIG_FILES']:
60             print "    ",f
61         print
62         print "Building a debian package would remove those files."
63         print
64         print "To continue, remove the offending file(s) and try again. Alternatively,"
65         print "build a source package using 'scons debsrc' and may then build debian"
66         print "binary packages from this source-package without disrupting your local"
67         print "configuration."
68         print
69         return 1
70
71 def getLibDepends(script):
72     # OUCH ...
73     return os.popen("perl -0777 -n -e '$,=\" \"; print $1=~m/'\"'\"'([^'\"'\"']*)'\"'\"'/g if /LIBS\s*=\s*\[([^\]]*)\]/' %s" % script).read().split()
74
75 # Original topological sort code written by Ofer Faigon
76 # (www.bitformation.com) and used with permission
77 def topological_sort(items, partial_order):
78     """Perform topological sort.
79        items is a list of items to be sorted.
80        partial_order is a list of pairs. If pair (a,b) is in it, it means
81        that item a should appear before item b.
82        Returns a list of the items in one of the possible orders, or None
83        if partial_order contains a loop.
84     """
85     def add_node(graph, node):
86         if not graph.has_key(node):
87             graph[node] = [0] 
88     def add_arc(graph, fromnode, tonode):
89         graph[fromnode].append(tonode)
90         graph[tonode][0] = graph[tonode][0] + 1
91     graph = {}
92     for v in items:
93         add_node(graph, v)
94     for a,b in partial_order:
95         add_arc(graph, a, b)
96     roots = [node for (node,nodeinfo) in graph.items() if nodeinfo[0] == 0]
97     while len(roots) != 0:
98         root = roots.pop()
99         yield root
100         for child in graph[root][1:]:
101             graph[child][0] = graph[child][0] - 1
102             if graph[child][0] == 0:
103                 roots.append(child)
104         del graph[root]
105     if len(graph.items()) != 0:
106         raise RuntimeError, "Loop detected in partial_order"
107
108 ###########################################################################
109 # Load utilities and setup libraries and configure build
110
111 SENFSCons.UseBoost()
112 SENFSCons.UseSTLPort()
113 env = SENFSCons.MakeEnvironment()
114
115 env.Help("""
116 Additional top-level build targets:
117
118 prepare      Create all source files not part of the repository
119 all_tests    Build and run unit tests for all modules
120 all_docs     Build documentation for all modules
121 all          Build everything
122 install_all  Install SENF into $PREFIX
123 deb          Build debian source and binary package
124 debsrc       Build debian source package
125 debbin       Build debian binary package
126 linklint     Check links of doxygen documentation with 'linklint'
127 fixlinks     Fix broken links in doxygen documentation
128 valgrind     Run all tests under valgrind/memcheck
129 """)
130
131 if os.environ.get('debian_build'):
132     rev = os.popen("dpkg-parsechangelog | awk '/^Version:/{print $2}'").read().strip()
133 else:
134     rev = 'r' + os.popen("svnversion").read().strip().lower()
135
136 logname = os.environ.get('LOGNAME')
137 if not logname:
138     logname = pwd.getpwuid(os.getuid()).pw_name
139
140 def configFilesOpts(target, source, env, for_signature):
141     return [ '-I%s' % os.path.split(f)[1] for f in env['LOCAL_CONFIG_FILES'] ]
142
143 # Options used to debug inlining:
144 #
145 # INLINE_OPTS = [ '-finline-limit=20000', '--param','large-function-growth=10000',
146 #                 '--param', 'large-function-insns=10000', '--param','inline-unit-growth=10000',
147 #                 '-fvisibility-inlines-hidden', '-fno-inline-functions', '-Winline' ]
148 #
149 # BEWARE: You need lots of ram to compile with these settings (approx 1G)
150 #
151
152 INLINE_OPTS = [ '-finline-limit=5000' ]
153
154 env.Append(
155    CPPPATH = [ '#/include' ],
156    CXXFLAGS = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long' ] + INLINE_OPTS,
157    LIBS = [ 'readline', 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB' ],
158    TEST_EXTRA_LIBS = [ '$BOOSTFSLIB' ],
159    DOXY_XREF_TYPES = [ 'bug', 'fixme', 'todo', 'idea' ],
160    DOXY_HTML_XSL = '#/doclib/html-munge.xsl',
161    ENV = { 'TODAY' : str(datetime.date.today()),
162            'REVISION' : rev,
163            'LOGNAME' : logname, # needed by the debian build scripts
164            'CONCURRENCY_LEVEL' : env.GetOption('num_jobs') or "1",
165            'SCONS' : 1,
166            'PATH' : os.environ.get('PATH')
167          },
168    LOCAL_CONFIG_FILES = [ 'Doxyfile.local', 'SConfig', 'local_config.hh' ],
169    CONFIG_FILES_OPTS = configFilesOpts,
170    CLEAN_PATTERNS = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign', '.sconsign.dblite' ],
171    BUILDPACKAGE_COMMAND = "dpkg-buildpackage -us -uc -rfakeroot -I.svn -I_templates $CONFIG_FILES_OPTS",
172    TOP_INCLUDES = [ 'Packets', 'PPI', 'Scheduler', 'Socket', 'Utils', 'Console',
173                     'config.hh', 'local_config.hh' ],
174 )
175
176 def parseLogOption(value):
177     stream, area, level = ( x.strip() for x in value.strip().split('|') )
178     if stream  : stream = ''.join('(%s)' % x for x in stream.split('::') )
179     else       : stream = '(_)'
180     if area : area = ''.join( '(%s)' % x for x in area.split('::') )
181     else    : area = '(_)'
182     return '(( %s,%s,%s ))' % (stream,area,level)
183
184 def expandLogOption(target, source, env, for_signature):
185     return ' '.join( parseLogOption(x) for x in env.subst('$LOGLEVELS').split() )
186
187 if env.subst('$LOGLEVELS'):
188     env.Append( expandLogOption=expandLogOption )
189     env.Append( CPPDEFINES = { 'SENF_LOG_CONF': '$expandLogOption' } )
190
191 env.SetDefault(
192        LIBSENF = "senf"
193 )
194
195 Export('env')
196
197 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
198 # Create it even when cleaning, to silence the doxygen builder warnings
199 if not os.path.exists("Doxyfile.local"):
200     Execute(Touch("Doxyfile.local"))
201
202 # Create local_config.h
203 if not env.GetOption('clean') and not os.path.exists("local_config.hh"):
204     Execute(Touch("local_config.hh"))
205
206 ###########################################################################
207 # Define build targets
208
209 # Before defining any targets, check wether this is the first build in
210 # pristine directory tree. If so, call 'scons prepare' so the dependencies
211 # created later are correct
212
213 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
214    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
215     env.Execute([ "scons prepare" ])
216
217 env.Clean('all', '.prepare-stamp')
218
219 # Not nice, but until we get to fixing the dependency jungle
220 # concerning generated sources ...
221 scripts = []
222 dependencies = []
223
224 for script in glob.glob("*/SConscript"):
225     depends = getLibDepends(script)
226     script = script.split('/',1)[0]
227     scripts.append(script)
228     dependencies += [ (dep, script) for dep in depends ]
229
230 for subdir in topological_sort(scripts, dependencies):
231     SConscript(os.path.join(subdir, "SConscript"))
232     
233 SENFSCons.StandardTargets(env)
234 SENFSCons.GlobalTargets(env)
235 SENFSCons.Doxygen(env)
236 SENFSCons.DoxyXRef(env,
237                    HTML_HEADER = '#/doclib/doxy-header.html',
238                    HTML_FOOTER = '#/doclib/doxy-footer.html')
239
240 SENFSCons.InstallIncludeFiles(env, [ 'config.hh' ])
241
242 # Build combined library 'libsenf'
243 libsenf = env.Library(
244     'senf${LIBADDSUFFIX}',
245     Flatten([ env.File(SENFSCons.LibPath(lib)).sources for lib in env['ALLLIBS'] ]))
246 env.Default(libsenf)
247 env.Clean('all', libsenf)
248 env.Alias('default', libsenf)
249
250 env.Alias('install_all', env.Install('$LIBINSTALLDIR', libsenf))
251
252 if env.GetOption('clean'):
253     env.Clean('all', [ os.path.join(path,f)
254                        for path, subdirs, files in os.walk('.')
255                        for pattern in env['CLEAN_PATTERNS']
256                        for f in fnmatch.filter(files,pattern) ])
257
258 PhonyTarget(env, 'deb', [
259     checkLocalConf,
260     updateRevision,
261     "$BUILDPACKAGE_COMMAND",
262     "fakeroot ./debian/rules debclean"
263 ])
264
265 PhonyTarget(env, 'debsrc', [
266     updateRevision,
267     "$BUILDPACKAGE_COMMAND -S",
268 ])
269
270 PhonyTarget(env, 'debbin', [
271     checkLocalConf,
272     updateRevision,
273     "$BUILDPACKAGE_COMMAND -b",
274     "fakeroot ./debian/rules debclean"
275 ])
276
277 PhonyTarget(env, 'linklint', [
278     'rm -rf linklint',
279     'linklint -doc linklint -limit 99999999 `find -type d -name html -printf "/%P/@ "`',
280     '[ ! -r linklint/errorX.html ] || python doclib/linklint_addnames.py <linklint/errorX.html >linklint/errorX.html.new',
281     '[ ! -r linklint/errorX.html.new ] || mv linklint/errorX.html.new linklint/errorX.html',
282     '[ ! -r linklint/errorAX.html ] || python doclib/linklint_addnames.py <linklint/errorAX.html >linklint/errorAX.html.new',
283     '[ ! -r linklint/errorAX.html.new ] || mv linklint/errorAX.html.new linklint/errorAX.html',
284     'echo -e "\\nLokal link check results: linklint/index.html\\nRemote link check results: linklint/urlindex.html\\n"',
285 ])
286
287 PhonyTarget(env, 'fixlinks', [
288     'python doclib/fix-links.py -v -s .svn -s linklint -s debian linklint/errorX.txt linklint/errorAX.txt',
289 ])
290
291 PhonyTarget(env, 'prepare', [])
292
293 PhonyTarget(env, 'valgrind', [
294     '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'
295     ], [ 'all_tests' ])
296
297 env.Clean('all', env.Dir('linklint'))
298
299 env.Clean('all','.prepare-stamp')
300 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
301     Execute(Touch(".prepare-stamp"))