From a6bb85c11b83e759dd74a58a1003fb16ab151d70 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sun, 4 Oct 2009 13:54:13 +0100 Subject: [PATCH] mdwsetup.py: Common utilities for Python module build systems. --- Makefile.am | 3 + mdwsetup.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 mdwsetup.py diff --git a/Makefile.am b/Makefile.am index 86b4fb0..862491d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 0000000..9ce67e6 --- /dev/null +++ b/mdwsetup.py @@ -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 -------------------------------------------------- -- 2.11.0