Makefile.am: Ship `debian/compat'.
[cfd] / mdwsetup.py
CommitLineData
a6bb85c1
MW
1### -*-python-*-
2###
3### Utility module for Python build systems
4###
5### (c) 2009 Straylight/Edgeware
6###
7
8###----- Licensing notice ---------------------------------------------------
9###
10### This file is part of the Python interface to mLib.
11###
12### mLib/Python is free software; you can redistribute it and/or modify
13### it under the terms of the GNU General Public License as published by
14### the Free Software Foundation; either version 2 of the License, or
15### (at your option) any later version.
16###
17### mLib/Python is distributed in the hope that it will be useful,
18### but WITHOUT ANY WARRANTY; without even the implied warranty of
19### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20### GNU General Public License for more details.
21###
22### You should have received a copy of the GNU General Public License
23### along with mLib/Python; if not, write to the Free Software Foundation,
24### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26import sys as SYS
27import os as OS
28import re as RE
29import subprocess as SUB
30
31import distutils.core as DC
32
33###--------------------------------------------------------------------------
34### Random utilities.
35
36def uniquify(seq):
37 """
38 Return a list of the elements of SEQ, with duplicates removed.
39
40 Only the first occurrence (according to `==') is left.
41 """
42 seen = {}
43 out = []
44 for item in seq:
45 if item not in seen:
46 seen[item] = True
47 out.append(item)
48 return out
49
50###--------------------------------------------------------------------------
51### Subprocess hacking.
52
53class SubprocessFailure (Exception):
54 def __init__(me, file, rc):
55 me.args = (file, rc)
56 me.file = file
57 me.rc = rc
58 def __str__(me):
59 if WIFEXITED(me.rc):
60 return '%s failed (rc = %d)' % (me.file, WEXITSTATUS(me.rc))
61 elif WIFSIGNALED(me.rc):
62 return '%s died (signal %d)' % (me.file, WTERMSIG(me.rc))
63 else:
64 return '%s died inexplicably' % (me.file)
65
66def progoutput(command):
67 """
68 Run the shell COMMAND and return its standard output.
69
70 The COMMAND must produce exactly one line of output, and must exit with
71 status zero.
72 """
73 kid = SUB.Popen(command, stdout = SUB.PIPE)
74 out = kid.stdout.readline()
75 junk = kid.stdout.read()
76 if junk != '':
77 raise ValueError, \
78 "Child process `%s' produced unspected output %r" % (command, junk)
79 rc = kid.wait()
80 if rc != 0:
81 raise SubprocessFailure, (command, rc)
82 return out.rstrip('\n')
83
84###--------------------------------------------------------------------------
85### External library packages.
86
87INCLUDEDIRS = []
88LIBDIRS = []
89LIBS = []
90
91def pkg_config(pkg, version):
92 """
93 Find the external package PKG and store the necessary compiler flags.
94
95 The include-directory names are stored in INCLUDEDIRS; the
96 library-directory names are in LIBDIRS; and the library names themselves
97 are in LIBS.
98 """
99 spec = '%s >= %s' % (pkg, version)
100 def weird(what, word):
101 raise ValueError, \
102 "Unexpected `%s' item `%s' from package `%s'" % (what, word, pkg)
103 for word in progoutput(['pkg-config', '--cflags', spec]).split():
104 if word.startswith('-I'):
105 INCLUDEDIRS.append(word[2:])
106 else:
107 weird('--cflags', word)
108 for word in progoutput(['pkg-config', '--libs', spec]).split():
109 if word.startswith('-L'):
110 LIBDIRS.append(word[2:])
111 elif word.startswith('-l'):
112 LIBS.append(word[2:])
113 else:
114 weird('--libs', word)
115
116###--------------------------------------------------------------------------
117### Substituting variables in files.
118
119def needs_update_p(target, sources):
120 """
121 Returns whether TARGET is out of date relative to its SOURCES.
122
123 If TARGET exists and was modified more recentently than any of its SOURCES
124 then it doesn't need updating.
125 """
126 if not OS.path.exists(target):
127 return True
128 t_target = OS.stat(target).st_mtime
129 for source in sources:
130 if OS.stat(source).st_mtime >= t_target:
131 return True
132 return False
133
134RX_SUBST = RE.compile(r'\%(\w+)\%')
135def derive(target, source, substmap):
136 """
137 Derive TARGET from SOURCE by making simple substitutions.
138
139 The SOURCE may contain markers %FOO%; these are replaced by SUBSTMAP['FOO']
140 in the TARGET file.
141 """
142 if not needs_update_p(target, [source]):
143 return False
144 print "making `%s' from `%s'" % (target, source)
145 temp = target + '.new'
146 ft = open(temp, 'w')
147 try:
148 fs = open(source, 'r')
149 try:
150 for line in fs:
151 ft.write(RX_SUBST.sub((lambda m: substmap[m.group(1)]), line))
152 finally:
153 fs.close()
154 finally:
155 ft.close()
156 OS.rename(temp, target)
157
158def generate(target, source = None):
159 """
160 Generate TARGET by running the SOURCE Python script.
161
162 If SOURCE is omitted, replace the extension of TARGET by `.py'.
163 """
164 if source is None:
165 source = OS.path.splitext(target)[0] + '.py'
166 if not needs_update_p(target, [source]):
167 return
168 print "making `%s' using `%s'" % (target, source)
169 temp = target + '.new'
170 ft = open(temp, 'w')
171 try:
172 rc = SUB.call([SYS.executable, source], stdout = ft)
173 finally:
174 ft.close()
175 if rc != 0:
176 raise SubprocessFailure, (source, rc)
177 OS.rename(temp, target)
178
179###--------------------------------------------------------------------------
180### Discovering version numbers.
181
182def auto_version(writep = True):
183 """
184 Returns the package version number.
185
186 As a side-effect, if WRITEP is true, then write the version number to the
187 RELEASE file so that it gets included in distributions.
188 """
189 version = progoutput(['./auto-version'])
190 if writep:
191 ft = open('RELEASE.new', 'w')
192 try:
193 ft.write('%s\n' % version)
194 finally:
195 ft.close()
196 OS.rename('RELEASE.new', 'RELEASE')
197 return version
198
199###----- That's all, folks --------------------------------------------------