chiark / gitweb /
mdwsetup.py: Use `with open(...) as f' instead of `try'/`finally'.
[cfd] / mdwsetup.py
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 Common Files Distribution (`common')
11 ###
12 ### `Common' 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 ### `Common' 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 `common'; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 from __future__ import with_statement
27
28 import sys as SYS
29 import os as OS
30 import re as RE
31 import subprocess as SUB
32
33 import distutils.core as DC
34
35 ###--------------------------------------------------------------------------
36 ### Random utilities.
37
38 def 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
55 class 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
68 def 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
89 INCLUDEDIRS = []
90 LIBDIRS = []
91 LIBS = []
92
93 def 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
121 def 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
136 RX_SUBST = RE.compile(r'\%(\w+)\%')
137 def 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'
148   with open(temp, 'w') as ft:
149     with open(source, 'r') as fs:
150       for line in fs:
151         ft.write(RX_SUBST.sub((lambda m: substmap[m.group(1)]), line))
152   OS.rename(temp, target)
153
154 def 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'
166   with open(temp, 'w') as ft:
167     rc = SUB.call([SYS.executable, source], stdout = ft)
168   if rc != 0:
169     raise SubprocessFailure, (source, rc)
170   OS.rename(temp, target)
171
172 ###--------------------------------------------------------------------------
173 ### Discovering version numbers.
174
175 def 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:
184     with open('RELEASE.new', 'w') as ft: ft.write('%s\n' % version)
185     OS.rename('RELEASE.new', 'RELEASE')
186   return version
187
188 ###----- That's all, folks --------------------------------------------------