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