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