Utils/Logger: Completed documentation
[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):
12     env.AlwaysBuild(env.Command(target + '.phony', 'SConstruct', 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['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['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 """)
116
117 if os.environ.get('debian_build'):
118     rev = os.popen("dpkg-parsechangelog | awk '/^Version:/{print $2}'").read().strip()
119 else:
120     rev = 'r' + os.popen("svnversion").read().strip().lower()
121
122 logname = os.environ.get('LOGNAME')
123 if not logname:
124     logname = pwd.getpwuid(os.getuid()).pw_name
125
126 def configFilesOpts(target, source, env, for_signature):
127     return [ '-I%s' % os.path.split(f)[1] for f in env['CONFIG_FILES'] ]
128
129 env.Append(
130    CPPPATH = [ '#/include' ],
131    LIBS = [ 'iberty', '$BOOSTREGEXLIB', '$BOOSTFSLIB' ],
132    DOXY_XREF_TYPES = [ 'bug', 'fixme', 'todo', 'idea' ],
133    DOXY_HTML_XSL = '#/doclib/html-munge.xsl',
134    ENV = { 'TODAY' : str(datetime.date.today()),
135            'REVISION' : rev,
136            'LOGNAME' : logname, # needed by the debian build scripts
137            'CONCURRENCY_LEVEL' : env.GetOption('num_jobs') or "1",
138            'SCONS' : 1,
139          },
140    CONFIG_FILES = [ 'Doxyfile.local', 'SConfig', 'local_config.hh' ],
141    CONFIG_FILES_OPTS = configFilesOpts,
142    CLEAN_PATTERNS = [ '*.pyc', 'semantic.cache', '.sconsign', '.sconsign.dblite' ],
143    BUILDPACKAGE_COMMAND = "dpkg-buildpackage -us -uc -rfakeroot -I.svn $CONFIG_FILES_OPTS",
144    TOP_INCLUDES = [ 'Packets', 'PPI', 'Scheduler', 'Socket', 'Utils',
145                     'config.hh', 'local_config.hh' ]
146 )
147
148 Export('env')
149
150 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
151 if not env.GetOption('clean') and not os.path.exists("Doxyfile.local"):
152     Execute(Touch("Doxyfile.local"))
153
154 # Create local_config.h
155 if not env.GetOption('clean') and not os.path.exists("local_config.hh"):
156     Execute(Touch("local_config.hh"))
157
158 ###########################################################################
159 # Define build targets
160
161 # Before defining any targets, check wether this is the first build in
162 # pristine directory tree. If so, call 'scons prepare' so the dependencies
163 # created later are correct
164
165 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
166    and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
167     env.Execute([ "scons prepare" ])
168
169 env.Clean('all', '.prepare-stamp')
170
171 # Not nice, but until we get to fixing the dependency jungle
172 # concerning generated sources ...
173 scripts = []
174 dependencies = []
175
176 for script in glob.glob("*/SConscript"):
177     depends = getLibDepends(script)
178     script = script.split('/',1)[0]
179     scripts.append(script)
180     dependencies += [ (dep, script) for dep in depends ]
181
182 for subdir in topological_sort(scripts, dependencies):
183     SConscript(os.path.join(subdir, "SConscript"))
184     
185 SENFSCons.StandardTargets(env)
186 SENFSCons.GlobalTargets(env)
187 SENFSCons.Doxygen(env)
188 SENFSCons.DoxyXRef(env,
189                    HTML_HEADER = '#/doclib/doxy-header-overview.html',
190                    HTML_FOOTER = '#/doclib/doxy-footer.html')
191
192 SENFSCons.InstallIncludeFiles(env, [ 'config.hh' ])
193
194 # Build combined library 'libsenf'
195 libsenf = env.Library(
196     SENFSCons.LibPath('senf'),
197     Flatten([ env.File(SENFSCons.LibPath(lib)).sources for lib in env['ALLLIBS'] ]))
198 env.Default(libsenf)
199 env.Clean('all', 'libsenf.a')
200 env.Alias('default', 'libsenf.a')
201
202 env.Alias('install_all', env.Install('$LIBINSTALLDIR', libsenf))
203
204 env.Clean('all', [ os.path.join(path,f)
205                    for path, subdirs, files in os.walk('.')
206                    for pattern in env['CLEAN_PATTERNS']
207                    for f in fnmatch.filter(files,pattern) ])
208
209 PhonyTarget(env, 'deb', [
210     checkLocalConf,
211     updateRevision,
212     "$BUILDPACKAGE_COMMAND",
213 ])
214
215 PhonyTarget(env, 'debsrc', [
216     updateRevision,
217     "$BUILDPACKAGE_COMMAND -S",
218 ])
219
220 PhonyTarget(env, 'debbin', [
221     checkLocalConf,
222     updateRevision,
223     "$BUILDPACKAGE_COMMAND -nc",
224 ])
225
226 PhonyTarget(env, 'linklint', [
227     'rm -rf linklint',
228     'linklint -doc linklint -limit 99999999 `find -type d -name html -printf "/%P/@ "`',
229     '[ ! -r linklint/errorX.html ] || python linklint_addnames.py <linklint/errorX.html >linklint/errorX.html.new',
230     '[ ! -r linklint/errorX.html.new ] || mv linklint/errorX.html.new linklint/errorX.html',
231     '[ ! -r linklint/errorAX.html ] || python linklint_addnames.py <linklint/errorAX.html >linklint/errorAX.html.new',
232     '[ ! -r linklint/errorAX.html.new ] || mv linklint/errorAX.html.new linklint/errorAX.html',
233     'echo -e "\\nLokal link check results: linklint/index.html\\nRemote link check results: linklint/urlindex.html\\n"',
234 ])
235
236 PhonyTarget(env, 'fixlinks', [
237     'python doclib/fix-links.py -v -s .svn -s linklint -s debian linklint/errorX.txt linklint/errorAX.txt',
238 ])
239
240 PhonyTarget(env, 'prepare', [])
241
242 env.Clean('all', env.Dir('linklint'))
243
244 env.Clean('all','.prepare-stamp')
245 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
246     Execute(Touch(".prepare-stamp"))