mdwsetup.py: Common utilities for Python module build systems.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 4 Oct 2009 12:54:13 +0000 (13:54 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 4 Oct 2009 12:55:33 +0000 (13:55 +0100)
Makefile.am
mdwsetup.py [new file with mode: 0644]

index 86b4fb0..862491d 100644 (file)
@@ -84,6 +84,9 @@ pkgdata_DATA          += getdate.y getdate.h
 pkgdata_SCRIPTS                += install-ac
 pkgdata_SCRIPTS                += maninst
 
+## Python support stuff.
+pkgdata_SCRIPTS                += mdwsetup.py
+
 ## confsubst
 pkgdata_SCRIPTS                += confsubst
 CLEANFILES             += confsubst
diff --git a/mdwsetup.py b/mdwsetup.py
new file mode 100644 (file)
index 0000000..9ce67e6
--- /dev/null
@@ -0,0 +1,199 @@
+### -*-python-*-
+###
+### Utility module for Python build systems
+###
+### (c) 2009 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import sys as SYS
+import os as OS
+import re as RE
+import subprocess as SUB
+
+import distutils.core as DC
+
+###--------------------------------------------------------------------------
+### Random utilities.
+
+def uniquify(seq):
+  """
+  Return a list of the elements of SEQ, with duplicates removed.
+
+  Only the first occurrence (according to `==') is left.
+  """
+  seen = {}
+  out = []
+  for item in seq:
+    if item not in seen:
+      seen[item] = True
+      out.append(item)
+  return out
+
+###--------------------------------------------------------------------------
+### Subprocess hacking.
+
+class SubprocessFailure (Exception):
+  def __init__(me, file, rc):
+    me.args = (file, rc)
+    me.file = file
+    me.rc = rc
+  def __str__(me):
+    if WIFEXITED(me.rc):
+      return '%s failed (rc = %d)' % (me.file, WEXITSTATUS(me.rc))
+    elif WIFSIGNALED(me.rc):
+      return '%s died (signal %d)' % (me.file, WTERMSIG(me.rc))
+    else:
+      return '%s died inexplicably' % (me.file)
+
+def progoutput(command):
+  """
+  Run the shell COMMAND and return its standard output.
+
+  The COMMAND must produce exactly one line of output, and must exit with
+  status zero.
+  """
+  kid = SUB.Popen(command, stdout = SUB.PIPE)
+  out = kid.stdout.readline()
+  junk = kid.stdout.read()
+  if junk != '':
+    raise ValueError, \
+          "Child process `%s' produced unspected output %r" % (command, junk)
+  rc = kid.wait()
+  if rc != 0:
+    raise SubprocessFailure, (command, rc)
+  return out.rstrip('\n')
+
+###--------------------------------------------------------------------------
+### External library packages.
+
+INCLUDEDIRS = []
+LIBDIRS = []
+LIBS = []
+
+def pkg_config(pkg, version):
+  """
+  Find the external package PKG and store the necessary compiler flags.
+
+  The include-directory names are stored in INCLUDEDIRS; the
+  library-directory names are in LIBDIRS; and the library names themselves
+  are in LIBS.
+  """
+  spec = '%s >= %s' % (pkg, version)
+  def weird(what, word):
+    raise ValueError, \
+          "Unexpected `%s' item `%s' from package `%s'" % (what, word, pkg)
+  for word in progoutput(['pkg-config', '--cflags', spec]).split():
+    if word.startswith('-I'):
+      INCLUDEDIRS.append(word[2:])
+    else:
+      weird('--cflags', word)
+  for word in progoutput(['pkg-config', '--libs', spec]).split():
+    if word.startswith('-L'):
+      LIBDIRS.append(word[2:])
+    elif word.startswith('-l'):
+      LIBS.append(word[2:])
+    else:
+      weird('--libs', word)
+
+###--------------------------------------------------------------------------
+### Substituting variables in files.
+
+def needs_update_p(target, sources):
+  """
+  Returns whether TARGET is out of date relative to its SOURCES.
+
+  If TARGET exists and was modified more recentently than any of its SOURCES
+  then it doesn't need updating.
+  """
+  if not OS.path.exists(target):
+    return True
+  t_target = OS.stat(target).st_mtime
+  for source in sources:
+    if OS.stat(source).st_mtime >= t_target:
+      return True
+  return False
+
+RX_SUBST = RE.compile(r'\%(\w+)\%')
+def derive(target, source, substmap):
+  """
+  Derive TARGET from SOURCE by making simple substitutions.
+
+  The SOURCE may contain markers %FOO%; these are replaced by SUBSTMAP['FOO']
+  in the TARGET file.
+  """
+  if not needs_update_p(target, [source]):
+    return False
+  print "making `%s' from `%s'" % (target, source)
+  temp = target + '.new'
+  ft = open(temp, 'w')
+  try:
+    fs = open(source, 'r')
+    try:
+      for line in fs:
+        ft.write(RX_SUBST.sub((lambda m: substmap[m.group(1)]), line))
+    finally:
+      fs.close()
+  finally:
+    ft.close()
+  OS.rename(temp, target)
+
+def generate(target, source = None):
+  """
+  Generate TARGET by running the SOURCE Python script.
+
+  If SOURCE is omitted, replace the extension of TARGET by `.py'.
+  """
+  if source is None:
+    source = OS.path.splitext(target)[0] + '.py'
+  if not needs_update_p(target, [source]):
+    return
+  print "making `%s' using `%s'" % (target, source)
+  temp = target + '.new'
+  ft = open(temp, 'w')
+  try:
+    rc = SUB.call([SYS.executable, source], stdout = ft)
+  finally:
+    ft.close()
+  if rc != 0:
+    raise SubprocessFailure, (source, rc)
+  OS.rename(temp, target)
+
+###--------------------------------------------------------------------------
+### Discovering version numbers.
+
+def auto_version(writep = True):
+  """
+  Returns the package version number.
+
+  As a side-effect, if WRITEP is true, then write the version number to the
+  RELEASE file so that it gets included in distributions.
+  """
+  version = progoutput(['./auto-version'])
+  if writep:
+    ft = open('RELEASE.new', 'w')
+    try:
+      ft.write('%s\n' % version)
+    finally:
+      ft.close()
+    OS.rename('RELEASE.new', 'RELEASE')
+  return version
+
+###----- That's all, folks --------------------------------------------------