From ad6d1b84001a051e76d9c1ca516997e71750287a Mon Sep 17 00:00:00 2001 From: Hans Ulrich Niedermann Date: Sun, 22 Jun 2008 16:04:13 +0200 Subject: Split nbblib into multiple source files This necessitated - fixing nbb/nbb.in -> nbb/nbb substitution - meddling with PYTHONPATH - other fixes --- .gitignore | 1 + Makefile.am | 5 - configure.ac | 3 +- nbb/Makefile-files | 50 ++- nbb/nbb.in | 24 +- nbb/nbblib/__init__.in | 854 ------------------------------------------------ nbb/nbblib/__init__.py | 6 + nbb/nbblib/bs.py | 113 +++++++ nbb/nbblib/commands.py | 234 +++++++++++++ nbb/nbblib/main.py | 203 ++++++++++++ nbb/nbblib/package.in | 8 + nbb/nbblib/plugins.py | 55 ++++ nbb/nbblib/progutils.py | 54 +++ nbb/nbblib/vcs.py | 216 ++++++++++++ test/Makefile.am | 6 +- test/nbb-basic.at | 4 + 16 files changed, 960 insertions(+), 876 deletions(-) delete mode 100644 nbb/nbblib/__init__.in create mode 100644 nbb/nbblib/__init__.py create mode 100644 nbb/nbblib/bs.py create mode 100644 nbb/nbblib/commands.py create mode 100644 nbb/nbblib/main.py create mode 100644 nbb/nbblib/package.in create mode 100644 nbb/nbblib/plugins.py create mode 100644 nbb/nbblib/progutils.py create mode 100644 nbb/nbblib/vcs.py diff --git a/.gitignore b/.gitignore index ce3120c..c5b78e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *~ +*.pyc Makefile.in Makefile aclocal.m4 diff --git a/Makefile.am b/Makefile.am index 52f61f2..5edfe15 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,11 +33,6 @@ include build-helpers/package-version.mk include Makefile-files include nbb/Makefile-files -if HAVE_PYTHON -PYTHONPATH = $(DESTDIR)$(pythondir) -export PYTHONPATH -endif - if HAVE_NDIM_MAN2TXT .man.txt: $(NDIM_MAN2TXT) "$<" > "$@.new" diff --git a/configure.ac b/configure.ac index 09598ff..d51c332 100644 --- a/configure.ac +++ b/configure.ac @@ -32,8 +32,7 @@ NDIM_DETECT_MAN2TXT()dnl AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([GNUmakefile]) -AC_CONFIG_FILES([nbb/nbblib/__init__.py:nbb/nbblib/__init__.in]) -AC_CONFIG_FILES([nbb/nbb:nbb/nbb.in], [chmod +x nbb/nbb]) +AC_CONFIG_FILES([nbb/nbblib/package.py:nbb/nbblib/package.in]) AC_CONFIG_FILES([test/atlocal]) AC_CONFIG_FILES([test/Makefile]) AC_OUTPUT diff --git a/nbb/Makefile-files b/nbb/Makefile-files index bc0bdc3..41c2a40 100644 --- a/nbb/Makefile-files +++ b/nbb/Makefile-files @@ -2,17 +2,55 @@ if HAVE_PYTHON +nodist_nbblib_PYTHON += nbb/nbblib/package.py +CLEANFILES += nbb/nbblib/package.py + +nbblib_PYTHON += nbb/nbblib/__init__.py +nbblib_PYTHON += nbb/nbblib/bs.py +nbblib_PYTHON += nbb/nbblib/commands.py +nbblib_PYTHON += nbb/nbblib/main.py +nbblib_PYTHON += nbb/nbblib/plugins.py +nbblib_PYTHON += nbb/nbblib/progutils.py +nbblib_PYTHON += nbb/nbblib/vcs.py + +# Put all python source files, whether changed or verbatim, +# into builddir, such that we can run tests in builddir. +all-local: all-local-nbblib +all-local-nbblib: + @for f in $(nbblib_PYTHON); do \ + if test -f "$(top_builddir)/$$f" && test "$(top_srcdir)/$$f" -ot "$(top_builddir)/$$f"; then :; else \ + echo "INFO: Updating $$f in $(top_builddir) from $(top_srcdir)"; \ + cp -f "$(top_srcdir)/$$f" "$(top_builddir)/$$f"; \ + fi; \ + done + bin_SCRIPTS += nbb/nbb -EXTRA_DIST += nbb/nbb.in CLEANFILES += nbb/nbb -nodist_nbblib_PYTHON += nbb/nbblib/__init__.py -CLEANFILES += nbb/nbblib/__init__.py - -nbb/nbb: nbb/nbblib/__init__.py +# We cannot create nbb/nbb from nbb/nbb.in in configure.ac/config.status. +# pythondir is defined as ${something}foobar, and that needs expansion. +EXTRA_DIST += nbb/nbb.in +nbb/nbb: nbb/nbb.in $(nodist_nbblib_PYTHON) $(nbblib_PYTHON) Makefile + sed \ + -e 's&[@]pythondir@&$(pythondir)&g' \ + -e 's&[@]PYTHON@&$(PYTHON)&g' \ + -e 's&[@]PACKAGE_VERSION@&$(PACKAGE_VERSION)&g' \ + < $(srcdir)/nbb/nbb.in > nbb/nbb.new + @if test "x$$(grep '@[a-zA-Z0-9_]\{1,\}@' nbb/nbb.new)" = "x"; then :; \ + else \ + echo "FATAL: Unsubstituted markers remain in nbb/nbb.new."; \ + grep '@[a-zA-Z0-9_]\{1,\}@' nbb/nbb.new; \ + exit 1; \ + fi + @if test -f nbb/nbb && cmp nbb/nbb.new nbb/nbb; \ + then rm -f nbb/nbb.new; \ + else mv -f nbb/nbb.new nbb/nbb; echo "INFO: Updating nbb/nbb"; fi + @chmod +x nbb/nbb endif -CLEANFILES += nbb/nbblib/__init__.pyc +clean-local: clean-local-nbblib +clean-local-nbblib: + rm -f nbb/nbblib/*.pyc # End of Makefile-files. diff --git a/nbb/nbb.in b/nbb/nbb.in index 35d733e..ac2c2f0 100644 --- a/nbb/nbb.in +++ b/nbb/nbb.in @@ -9,29 +9,37 @@ License conditions TBA import sys import os +PACKAGE_VERSION = "@PACKAGE_VERSION@" + if __name__ == '__main__': pythondir = "@pythondir@" lib_found = False + #print "pythondir", pythondir + #print "sys.path", sys.path + sys.stdout.flush() + orig_path = sys.path for cond, path in [ - (1, sys.path), - (os.path.exists(pythondir), [pythondir] + sys.path), + (True, orig_path), + (os.path.exists(pythondir), [pythondir] + orig_path), ]: if cond: sys.path = path try: import nbblib + #print "nbblib.PACKAGE_VERSION", nbblib.PACKAGE_VERSION + #print "PACKAGE_VERSION", PACKAGE_VERSION + assert(nbblib.PACKAGE_VERSION == PACKAGE_VERSION) lib_found = True break + except AssertionError, e: + sys.path = orig_path except ImportError, e: - pass + sys.path = orig_path if not lib_found: sys.stderr.write("nbb: Fatal: Could not load nbblib.\n") sys.exit(3) - #sys.stdout.write("sys.path=%s\n" % repr(sys.path)) - #sys.stdout.flush() - PACKAGE_VERSION = "@PACKAGE_VERSION@" - assert(nbblib.PACKAGE_VERSION == PACKAGE_VERSION) - nbblib.main(sys.argv) + import nbblib.main + nbblib.main.main(sys.argv) # vim: syntax=python # Local Variables: diff --git a/nbb/nbblib/__init__.in b/nbb/nbblib/__init__.in deleted file mode 100644 index ed21743..0000000 --- a/nbb/nbblib/__init__.in +++ /dev/null @@ -1,854 +0,0 @@ -"""\ -nbb (ndim's branch builder) %(PACKAGE_VERSION)s -Build, install given branch of source code into a branch specific place -Copyright (C) 2007, 2008 Hans Ulrich Niedermann -License conditions TBA - -Usage: %(prog)s - -Features: - * supports git branches (TBD: git config) - * supports bzr branches (requires useful branch nick, TBD: bzr config) - * does out-of-source-tree builds (in-source-tree-builds unsupported) - * direct support for automake/autoconf based build systems - * TBD: supports execution of user commands in source, build, install dirs - -TODO: - * VCS config support ('git config', etc.) - * Build system support: automake/autoconf, cmake - * Design nice user interface. Requirements: - * print top_srcdir, builddir, installdir - * start subshell in top_srcdir, builddir, installdir - * run 'autoreconf' type step - * run 'configure' type step - * run 'make' type step - * run 'make install' type step - * run custom (make) commands - * Bash syntax completion for that user interface. - * Man page or something similar. - -Command line interface (TBD): - - Run default build commands: - $ %(prog)s [general options] init [command specific options] - $ %(prog)s [general options] configure [command specific options] - $ %(prog)s [general options] build [command specific options] - $ %(prog)s [general options] install [command specific options] - - Run cleanup commands: - TBD - - Get/set config: - $ %(prog)s [general options] config srcdir - $ %(prog)s [general options] config builddir [] - $ %(prog)s [general options] config installdir [] - - Start an interactive shell in either of the three directories: - $ %(prog)s [general options] src-sh [command specific options] - $ %(prog)s [general options] build-sh [command specific options] - $ %(prog)s [general options] install-sh [command specific options] - - Run command in builddir: - $ %(prog)s [general options] run [...] - $ %(prog)s [general options] run [command specific options... <-->] ... - - (Not sure about these) - Run a non-interactive shell command in either of the three directories: - $ %(prog)s [general options] src-sh [command specific options] ... - $ %(prog)s [general options] build-sh [command specific options] ... - $ %(prog)s [general options] install-sh [command specific options] - -Global options: - - -h --help Print this help text - -V --version Print program version number - - -n --dry-run Do not actually execute any commands - - -b --build-system Force buildsystem detection (%(buildsystems)s) - -v --vcs Force VCS detection (%(vcssystems)s) -""" - -import sys -import os -import getopt - -import subprocess -import urlparse - - -# Used to make sure nbb_lib and nbb fit together -PACKAGE_VERSION = "@PACKAGE_VERSION@" - -GIT_CONFIG_PREFIX = 'nbb' - - -######################################################################## -# Utility functions -######################################################################## - - -def adjust_doc(doc): - """Remove common whitespace at beginning of doc string lines""" - if not doc: return doc - i = 0 - for i in range(len(doc)): - if doc[i] not in " \t": - break - prefix = doc[:i] - rest_doc = doc[i:] - almost_doc = rest_doc.replace("\n%s" % prefix, "\n") - i = -1 - while almost_doc[i] == '\n': - i = i - 1 - return almost_doc[:i] - - -def prog_stdout(call_list): - """Run program and return stdout (similar to shell backticks)""" - p = subprocess.Popen(call_list, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = p.communicate(input=None) - return stdout.strip() - - -def prog_retstd(call_list): - """Run program and return stdout (similar to shell backticks)""" - p = subprocess.Popen(call_list, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = p.communicate(input=None) - return (p.returncode, stdout.strip(), stderr.strip()) - - -class ProgramRunError(Exception): - """A program run returns a retcode != 0""" - def __init__(self, call_list, retcode, cwd=None): - self.call_list = call_list - self.retcode = retcode - if cwd: - self.cwd = cwd - else: - self.cwd = os.getcwd() - def __str__(self): - return ("Error running program (%s, retcode=%d, cwd=%s)" - % (repr(self.call_list), - self.retcode, - repr(self.cwd))) - - -def prog_run(call_list, context): - """Run program showing its output. Raise exception if retcode != 0.""" - print "RUN:", call_list - print " in", os.getcwd() - if context.dry_run: - return None - p = subprocess.Popen(call_list) - stdout, stderr = p.communicate(input=None) - if p.returncode != 0: - raise ProgramRunError(call_list, p.returncode, os.getcwd()) - return p.returncode - - -class AbstractConfig(object): - """Return static config until we implement real config reading""" - - def __init__(self, srcdir, nick): - super(AbstractConfig, self).__init__() - self._srcdir = srcdir - self._nick = nick - - def get_srcdir(self): - return os.path.join(self._srcdir) - srcdir = property(get_srcdir) - - def get_builddir(self): - return os.path.join(self._srcdir, "_build", self._nick) - builddir = property(get_builddir) - - def get_installdir(self): - return os.path.join(self._srcdir, "_install", self._nick) - installdir = property(get_installdir) - - -class CommandLineError(Exception): - def __init__(self, message, *args): - super(CommandLineError, self).__init__() - if args: - self.msg = message % args - else: - self.msg = message - def __str__(self): - return "Command line error: %s" % self.msg - - -class DuplicatePluginName(Exception): - pass - - -class PluginDict(object): - """Helper for GenericPluginMeta class - - Behaves basically like a standard dict, but fails when asked - to update an existing value. - """ - def __init__(self): - self.dict = {} - def __getitem__(self, *args): - return self.dict.__getitem__(*args) - def __setitem__(self, key, value): - if self.dict.has_key(key): - raise DuplicatePluginName() - else: - self.dict[key] = value - def items(self): return self.dict.items() - def keys(self): return self.dict.keys() - def values(self): return self.dict.values() - def __iter__(self): return self.dict.__iter__() - def __str__(self): return self.dict.__str__() - def __repr__(self): return self.dict.__repr__() - def __len__(self): return self.dict.__len__() - def has_key(self, key): return self.dict.has_key(key) - - -######################################################################## -# Generic plugin system -######################################################################## -# Plugin architecture (metaclass tricks) by Marty Alchin from -# http://gulopine.gamemusic.org/2008/jan/10/simple-plugin-framework/ -# Slightly modified go store plugins as dict. -######################################################################## - -class GenericPluginMeta(type): - def __init__(cls, name, bases, attrs): - if not hasattr(cls, 'plugins'): - # This branch only executes when processing the mount point itself. - # So, since this is a new plugin type, not an implementation, this - # class shouldn't be registered as a plugin. Instead, it sets up a - # list where plugins can be registered later. - cls.plugins = PluginDict() - elif hasattr(cls, 'name'): - # This must be a plugin implementation, which should be registered. - # Simply appending it to the list is all that's needed to keep - # track of it later. - cls.plugins[cls.name] = cls - else: - # This must be an abstract subclass of plugins. - pass - - -######################################################################## -# VCS Source Tree plugin system -######################################################################## - -class NotAVCSourceTree(Exception): - pass - - -class AmbigousVCSource(Exception): - def __init__(self, srcdir, matches): - super(AmbigousVCSource, self).__init__() - self.srcdir = srcdir - self.matches = matches - def __str__(self): - fmt = " %-9s %-15s %s" - def strmatch(m): - return fmt % (m.name, m.branch_name(), m.tree_root()) - alist = [fmt % ('VCS Type', 'Branch Name', 'Source tree root')] + map(strmatch, self.matches) - return ("More than one source tree VCS type detected for '%s':\n#%s" - % (self.srcdir, '\n '.join(alist))) - - -class VCSourceTree(object): - """ - Mount point for plugins which refer to actions that can be performed. - - Plugins implementing this reference should provide the following - interface: - - name attribute - The text to be displayed, describing the version control system - __init__ function - Must raise NotAVCSourceTree() if it is not a VCS source tree - """ - __metaclass__ = GenericPluginMeta - - def __init__(self, context): - super(VCSourceTree, self).__init__() - self.context = context - - @classmethod - def detect(cls, srcdir, context): - """Detect VCS tree type and return object representing it""" - if len(VCSourceTree.plugins) < 1: - raise "No VC source tree classes registered" - matches = PluginDict() - for key, klass in VCSourceTree.plugins.items(): - try: - t = klass(srcdir, context) - if t.tree_root() == srcdir: - matches[key] = t - except NotAVCSourceTree, e: - pass - if len(matches) > 1: - raise AmbigousVCSource(srcdir, matches.values()) - elif len(matches) < 1: - raise NotAVCSourceTree(srcdir) - return matches[matches.keys()[0]] - - def get_config(self): - """Get configuration object which determines builddir etc""" - return AbstractConfig(self.tree_root(), self.branch_name()) - config = property(get_config) - - def tree_root(self): - """Get absolute path to source tree root""" - raise NotImplementedError() - - def branch_name(self): - """Return name identifying the branch""" - raise NotImplementedError() - - def __str__(self): - return repr(self) - - def __repr__(self): - return "<%s(%s, %s)>" % (self.__class__.__name__, - repr(self.tree_root()), - repr(self.branch_name())) - - -######################################################################## -# Command plugin system -######################################################################## - -class Command(object): - """ - Mount point for plugins which refer to commands that can be performed. - - Plugins implementing this reference should provide the following - interface: - - name attribute - The text to be displayed, describing the version control system - summary attribute - Short (less than 50 chars) command summary line - usage attribute - Usage string (defaults to '') - - validate_args(*args, **kwargs) function - Must raise CommandLineError() if it encounters invalid arguments in cmdargs - run() function - Actually run the function - - FFF(*args, **kwargs) - *args are the arguments from the command line - **kwargs are additional parameters from within the program - """ - __metaclass__ = GenericPluginMeta - - usage = '' - - def __init__(self, *args, **kwargs): - self.validate_args(*args, **kwargs) - self.args = args - self.kwargs = kwargs - self.context = kwargs['context'] - - def run(self): - """Run the command""" - raise NotImplementedError() - - def validate_args(self, *args, **kwargs): - """Validate command line arguments""" - print "Command: ", self.name - print "*args: ", args - print "**kwargs:", kwargs - if len(args) > 0: - raise CommandLineError("'%s' command takes no parameters", self.name) - - def __str__(self): - return "Command(%s, %s)" % (self.cmd_name, self.cmdargs) - - -class HelpCommand(Command): - """\ - If the optional is given, print the help for . - Else, print a list of commands and general help. - """ - - name = 'help' - summary = 'print help text' - usage = '[]' - - def validate_args(self, *args, **kwargs): - if len(args) == 1 and args[0] not in Command.plugins.keys(): - raise CommandLineError("'%s' is an invalid command name", args[0]) - elif len(args) > 1: - raise CommandLineError("'%s' command only takes one optional parameter", self.name) - - def _print_command_list(self): - print "List of commands:" - keys = Command.plugins.keys() - keys.sort() - for k in keys: - print "\t%-15s\t%s" % (k, Command.plugins[k].summary) - - def _print_command_help(self, cmd): - """print help for command cmd""" - c = Command.plugins[cmd] - print "Purpose:", c.summary - if c.usage: - print "Usage: ", outdict['prog'], cmd, c.usage - else: - print "Usage: ", outdict['prog'], cmd - if hasattr(c, '__doc__'): - if c.__doc__: - print - print adjust_doc(c.__doc__) - - def run(self): - if len(self.args) == 0: - self._print_command_list() - elif len(self.args) == 1: - self._print_command_help(self.args[0]) - else: - assert(False) - - -class GitConfig(AbstractConfig): - """""" - - def __init__(self, *args, **kwargs): - super(GitConfig, self).__init__(*args, **kwargs) - - def _itemname(self, item): return '.'.join((GIT_CONFIG_PREFIX, item, )) - def _myreldir(self, rdir): - return os.path.join(self._srcdir, rdir, self._nick) - - def get_builddir(self): - ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('builddir')]) - assert(stderr == "") - if ret == 0 and stdout: - return self._myreldir(stdout) - else: - return super(GitConfig, self).get_builddir() - - def set_builddir(self, value): - ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('builddir'), value]) - assert(ret == 0 and stdout == "" and stderr == "") - - builddir = property(get_builddir, set_builddir) - - def get_installdir(self): - ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('installdir')]) - assert(stderr == "") - if ret == 0 and stdout: - return self._myreldir(stdout) - else: - return super(GitConfig, self).get_installdir() - - def set_installdir(self, value): - ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('installdir'), value]) - assert(ret == 0 and stdout == "" and stderr == "") - - installdir = property(get_installdir, set_installdir) - - -class InternalConfigCommand(Command): - name = 'internal-config' - summary = 'print internal program configuration' - def run(self): - print "Source tree types:", ", ".join(VCSourceTree.plugins.keys()) - print "Build system types:", ", ".join(BSSourceTree.plugins.keys()) - print "Commands:", ", ".join(Command.plugins.keys()) - - -class SourceClassCommand(Command): - """Base class for commands acting on source trees""" - def __init__(self, *args, **kwargs): - super(SourceClassCommand, self).__init__(*args, **kwargs) - context = kwargs['context'] - srcdir = os.getcwd() - absdir = os.path.abspath(srcdir) - self.vcs_sourcetree = VCSourceTree.detect(absdir, context) - if context.verbose: - print "vcs_sourcetree", str(self.vcs_sourcetree) - assert(self.vcs_sourcetree) - cfg = self.vcs_sourcetree.config - self.bs_sourcetree = BSSourceTree.detect(self.vcs_sourcetree, context) - if context.verbose: - print "bs_sourcetree", str(self.bs_sourcetree) - print "CONFIG", cfg - print " ", "srcdir", cfg.srcdir - print " ", "builddir", cfg.builddir - print " ", "installdir", cfg.installdir - - -class BuildTestCommand(SourceClassCommand): - name = 'build-test' - summary = 'simple build test' - def run(self): - self.bs_sourcetree.init() - self.bs_sourcetree.configure() - self.bs_sourcetree.build() - self.bs_sourcetree.install() - - -class ConfigCommand(SourceClassCommand): - name = 'config' - summary = 'set/get config values' - usage = '(srcdir|builddir|installdir)' - - def validate_args(self, *args, **kwargs): - items = ('srcdir', 'builddir', 'installdir', ) - if len(args) == 0: - raise CommandLineError("'%s' requires at least one parameter (%s)", self.name, - ', '.join(items)) - elif len(args) == 1 and args[0] in items: - pass - elif len(args) == 2 and args[0] in items: - if args[0] in ('srcdir', ): - raise CommandLineError("'%s' command cannot change 'srcdir'", self.name) - else: - pass - else: - raise CommandLineError("'%s' requires less or different parameters", self.name) - - def run(self): - git_get_items = ('builddir', 'installdir', 'srcdir') - git_set_items = ('builddir', 'installdir', ) - if len(self.args) == 1: - if self.args[0] in git_get_items: - print getattr(self.vcs_sourcetree.config, self.args[0]) - else: - assert(False) - elif len(self.args) == 2: - if self.args[0] == 'builddir': - self.vcs_sourcetree.config.builddir = self.args[1] - elif self.args[0] == 'installdir': - self.vcs_sourcetree.config.installdir = self.args[1] - else: - assert(False) - else: - assert(False) - - -######################################################################## -# VCS Source Tree plugins -######################################################################## - -class GitSourceTree(VCSourceTree): - - name = 'git' - - def __init__(self, srcdir, context): - super(GitSourceTree, self).__init__(context) - os.chdir(srcdir) - if "true" != prog_stdout(["git", "rev-parse", - "--is-inside-work-tree"]): - raise NotAVCSourceTree() - reldir = prog_stdout(["git", "rev-parse", "--show-cdup"]) - if reldir: - os.chdir(reldir) - self.__tree_root = os.getcwd() - - def get_config(self): - return GitConfig(self.tree_root(), self.branch_name()) - config = property(get_config) - - def tree_root(self): - return self.__tree_root - - def branch_name(self): - bname = prog_stdout(["git", "symbolic-ref", "HEAD"]) - refs,heads,branch = bname.split('/') - assert(refs=='refs' and heads=='heads') - return branch - - -class BzrSourceTree(VCSourceTree): - - name = 'bzr' - - def __init__(self, srcdir, context): - super(BzrSourceTree, self).__init__(context) - try: - import bzrlib.workingtree - wt,b = bzrlib.workingtree.WorkingTree.open_containing(srcdir) - except bzrlib.errors.NotBranchError: - raise NotAVCSourceTree() - except ImportError: - raise NotAVCSourceTree() - self.wt = wt - #print "wt:", wt - #print "wt:", dir(wt) - #print "wt.branch:", wt.branch - #print "wt.branch:", dir(wt.branch) - #print "wt.branch.nick:", wt.branch.nick - #print "wt.branch.abspath:", wt.branch.abspath - #print "wt.branch.base:", wt.branch.base - #print "wt.branch.basis_tree:", wt.branch.basis_tree() - - def tree_root(self): - proto,host,path,some,thing = urlparse.urlsplit(self.wt.branch.base) - assert(proto == "file" and host == "") - assert(some == "" and thing == "") - return os.path.abspath(path) - - def branch_name(self): - return self.wt.branch.nick - - -######################################################################## -# Buildsystem Source Tree plugins -######################################################################## - -class NotABSSourceTree(Exception): - def __init__(self, vcs_tree): - super(NotABSSourceTree, self).__init__() - self.vcs_tree = vcs_tree - def __str__(self): - return "Source tree build system type for '%s' not detected" % (self.vcs_tree,) - - -class AmbigousBSSource(Exception): - def __init__(self, srcdir, matches): - super(AmbigousBSSource, self).__init__() - self.srcdir = srcdir - self.matches = matches - def __str__(self): - fmt = " %-9s %s" - def strmatch(m): - return fmt % (m.name, m.tree_root()) - alist = [fmt % ('VCS Type', 'Source tree root')] + map(strmatch, self.matches) - return ("More than one source tree VCS type detected for '%s':\n#%s" - % (self.srcdir, '\n '.join(alist))) - - -class BSSourceTree(object): - __metaclass__ = GenericPluginMeta - - def __init__(self, context): - super(BSSourceTree, self).__init__() - self.context = context - - @classmethod - def detect(cls, vcs_tree, context): - """Find BS tree type and return it""" - if len(BSSourceTree.plugins) < 1: - raise "No BS source tree classes registered" - matches = PluginDict() - for key, klass in BSSourceTree.plugins.items(): - try: - t = klass(vcs_tree, context) - if t.tree_root() == vcs_tree.tree_root(): - matches[key] = t - except NotABSSourceTree, e: - pass - if len(matches) > 1: - raise ("More than one source tree BS type detected for '%s': %s" - % (vcs_tree, ", ".join(map(lambda x:str(x), matches)))) - elif len(matches) < 1: - raise NotABSSourceTree(vcs_tree) - return matches[matches.keys()[0]] - - def __str__(self): - return "BS-Source-Tree(%s, %s)" % (self.name, - repr(self.tree_root())) - - # Abstract methods - def tree_root(self): raise NotImplementedError() - def init(self): raise NotImplementedError() - def configure(self): raise NotImplementedError() - def build(self): raise NotImplementedError() - def install(self): raise NotImplementedError() - - -class AutomakeSourceTree(BSSourceTree): - name = 'automake' - def __init__(self, vcs_tree, context): - super(AutomakeSourceTree, self).__init__(context) - srcdir = vcs_tree.tree_root() - self.config = vcs_tree.config - flag = False - for f in [ os.path.join(srcdir, 'configure.ac'), - os.path.join(srcdir, 'configure.in'), - ]: - if os.path.exists(f): - flag = True - break - if not flag: - raise NotABSSourceTree(vcs_tree) - - def tree_root(self): - return self.config.srcdir - - def init(self): - """'autoreconf'""" - prog_run(["autoreconf", "-v", "-i", "-s", self.config.srcdir], self.context) - - def configure(self): - """'configure --prefix'""" - builddir = self.config.builddir - if not os.path.exists(builddir): os.makedirs(builddir) - os.chdir(builddir) - prog_run(["%s/configure" % self.config.srcdir, - "--prefix=%s" % self.config.installdir - ], self.context) - - def build(self): - """'make'""" - os.chdir(self.config.builddir) - prog_run(["make", ], self.context) - - def install(self): - """'make install'""" - os.chdir(self.config.builddir) - prog_run(["make", "install", "INSTALL=/usr/bin/install -p"], self.context) - - -######################################################################## -# Commands -######################################################################## - -class NBB_Command(object): - def __init__(self, cmd, cmdargs, context): - if Command.plugins.has_key(cmd): - try: - c = Command.plugins[cmd](*cmdargs, **{'context':context}) - c.run() - except CommandLineError, e: - print "%(prog)s: Fatal:" % (outdict), e - sys.exit(2) - except ProgramRunError, e: - print "%(prog)s: Fatal:" % (outdict), e - print "Program aborted." - else: - print "Fatal: Unknown command '%s'" % cmd - raise NotImplementedError() - return - - -######################################################################## -# Main program -######################################################################## - - -outdict = {} -outdict['vcssystems'] = ", ".join(VCSourceTree.plugins.keys()) -outdict['buildsystems'] = ", ".join(BSSourceTree.plugins.keys()) -outdict['PACKAGE_VERSION'] = PACKAGE_VERSION - -def print_help(): - print __doc__ % outdict, - - -class Property(object): - def __init__(self, **kwargs): - if kwargs.has_key('default'): - self.default = kwargs['default'] - def __get__(self, instance, owner): - if hasattr(self, 'value'): - return self.value - elif hasattr(self, 'default'): - return self.default - else: - return None - def __set__(self, instance, value): - if hasattr(self, 'value'): - raise "Property cannot be set more than once" - elif not self.isvalid(value): - raise "Property cannot be set to invalid value '%s'" % value - else: - self.value = value - def __str__(self): - if hasattr(self, 'value'): - return self.value - else: - return '' - def isvalid(self, value): - return True - - -class VCSProperty(Property): - def isvalid(self, value): - return (value in VCSourceTree.plugins.keys()) - - -class BSProperty(Property): - def isvalid(self, value): - return (value in BSSourceTree.plugins.keys()) - - -class BoolProperty(Property): - def __init__(self, default=False): - super(BoolProperty, self).__init__(default=default) - def isvalid(self, value): - return (value in (True, False)) - -class DryRunProperty(BoolProperty): - def __init__(self): - super(DryRunProperty, self).__init__(default=False) - - -class Context(object): - vcs = VCSProperty() - bs = BSProperty() - dry_run = DryRunProperty() - verbose = BoolProperty() - -def main(argv): - - prog = argv[0] - idx = prog.rfind('/') - if idx >= 0: - prog = prog[idx+1:] - outdict['prog'] = prog - - if len(argv) < 2: - print "Fatal: %(prog)s requires some arguments" % outdict - return 2 - - i = 1 - ctx = Context() - while i 1: + raise ("More than one source tree BS type detected for '%s': %s" + % (vcs_tree, ", ".join(map(lambda x:str(x), matches)))) + elif len(matches) < 1: + raise NotABSSourceTree(vcs_tree) + return matches[matches.keys()[0]] + + def __str__(self): + return "BS-Source-Tree(%s, %s)" % (self.name, + repr(self.tree_root())) + + # Abstract methods + def tree_root(self): raise NotImplementedError() + def init(self): raise NotImplementedError() + def configure(self): raise NotImplementedError() + def build(self): raise NotImplementedError() + def install(self): raise NotImplementedError() + + +class AutomakeSourceTree(BSSourceTree): + name = 'automake' + def __init__(self, vcs_tree, context): + super(AutomakeSourceTree, self).__init__(context) + srcdir = vcs_tree.tree_root() + self.config = vcs_tree.config + flag = False + for f in [ os.path.join(srcdir, 'configure.ac'), + os.path.join(srcdir, 'configure.in'), + ]: + if os.path.exists(f): + flag = True + break + if not flag: + raise NotABSSourceTree(vcs_tree) + + def tree_root(self): + return self.config.srcdir + + def init(self): + """'autoreconf'""" + prog_run(["autoreconf", "-v", "-i", "-s", self.config.srcdir], self.context) + + def configure(self): + """'configure --prefix'""" + builddir = self.config.builddir + if not os.path.exists(builddir): os.makedirs(builddir) + os.chdir(builddir) + prog_run(["%s/configure" % self.config.srcdir, + "--prefix=%s" % self.config.installdir + ], self.context) + + def build(self): + """'make'""" + os.chdir(self.config.builddir) + prog_run(["make", ], self.context) + + def install(self): + """'make install'""" + os.chdir(self.config.builddir) + prog_run(["make", "install", "INSTALL=/usr/bin/install -p"], self.context) + + diff --git a/nbb/nbblib/commands.py b/nbb/nbblib/commands.py new file mode 100644 index 0000000..9fcb038 --- /dev/null +++ b/nbb/nbblib/commands.py @@ -0,0 +1,234 @@ +import os + +from nbblib.package import * +from nbblib.plugins import * +from nbblib.progutils import * +from nbblib.vcs import * +from nbblib.bs import * + + +def adjust_doc(doc): + """Remove common whitespace at beginning of doc string lines""" + if not doc: return doc + i = 0 + for i in range(len(doc)): + if doc[i] not in " \t": + break + prefix = doc[:i] + rest_doc = doc[i:] + almost_doc = rest_doc.replace("\n%s" % prefix, "\n") + i = -1 + while almost_doc[i] == '\n': + i = i - 1 + return almost_doc[:i] + + +class CommandLineError(Exception): + def __init__(self, message, *args): + super(CommandLineError, self).__init__() + if args: + self.msg = message % args + else: + self.msg = message + def __str__(self): + return "Command line error: %s" % self.msg + + +######################################################################## +# Command plugin system +######################################################################## + +class Command(object): + """ + Mount point for plugins which refer to commands that can be performed. + + Plugins implementing this reference should provide the following + interface: + + name attribute + The text to be displayed, describing the version control system + summary attribute + Short (less than 50 chars) command summary line + usage attribute + Usage string (defaults to '') + + validate_args(*args, **kwargs) function + Must raise CommandLineError() if it encounters invalid arguments in cmdargs + run() function + Actually run the function + + FFF(*args, **kwargs) + *args are the arguments from the command line + **kwargs are additional parameters from within the program + """ + __metaclass__ = GenericPluginMeta + + usage = '' + + def __init__(self, *args, **kwargs): + self.validate_args(*args, **kwargs) + self.args = args + self.kwargs = kwargs + self.context = kwargs['context'] + + def run(self): + """Run the command""" + raise NotImplementedError() + + def validate_args(self, *args, **kwargs): + """Validate command line arguments""" + print "Command: ", self.name + print "*args: ", args + print "**kwargs:", kwargs + if len(args) > 0: + raise CommandLineError("'%s' command takes no parameters", self.name) + + def __str__(self): + return "Command(%s, %s)" % (self.cmd_name, self.cmdargs) + + +class HelpCommand(Command): + """\ + If the optional is given, print the help for . + Else, print a list of commands and general help. + """ + + name = 'help' + summary = 'print help text' + usage = '[]' + + def validate_args(self, *args, **kwargs): + if len(args) == 1 and args[0] not in Command.plugins.keys(): + raise CommandLineError("'%s' is an invalid command name", args[0]) + elif len(args) > 1: + raise CommandLineError("'%s' command only takes one optional parameter", self.name) + + def _print_command_list(self): + print "List of commands:" + keys = Command.plugins.keys() + keys.sort() + for k in keys: + print "\t%-15s\t%s" % (k, Command.plugins[k].summary) + + def _print_command_help(self, cmd): + """print help for command cmd""" + c = Command.plugins[cmd] + print "Purpose:", c.summary + if c.usage: + print "Usage: ", self.context.prog, cmd, c.usage + else: + print "Usage: ", self.context.prog, cmd + if hasattr(c, '__doc__'): + if c.__doc__: + print + print adjust_doc(c.__doc__) + + def run(self): + if len(self.args) == 0: + self._print_command_list() + elif len(self.args) == 1: + self._print_command_help(self.args[0]) + else: + assert(False) + + +class InternalConfigCommand(Command): + name = 'internal-config' + summary = 'print internal program configuration' + def run(self): + print "Source tree types:", ", ".join(VCSourceTree.plugins.keys()) + print "Build system types:", ", ".join(BSSourceTree.plugins.keys()) + print "Commands:", ", ".join(Command.plugins.keys()) + + +class SourceClassCommand(Command): + """Base class for commands acting on source trees""" + def __init__(self, *args, **kwargs): + super(SourceClassCommand, self).__init__(*args, **kwargs) + context = kwargs['context'] + srcdir = os.getcwd() + absdir = os.path.abspath(srcdir) + self.vcs_sourcetree = VCSourceTree.detect(absdir, context) + if context.verbose: + print "vcs_sourcetree", str(self.vcs_sourcetree) + assert(self.vcs_sourcetree) + cfg = self.vcs_sourcetree.config + self.bs_sourcetree = BSSourceTree.detect(self.vcs_sourcetree, context) + if context.verbose: + print "bs_sourcetree", str(self.bs_sourcetree) + print "CONFIG", cfg + print " ", "srcdir", cfg.srcdir + print " ", "builddir", cfg.builddir + print " ", "installdir", cfg.installdir + + +class BuildTestCommand(SourceClassCommand): + name = 'build-test' + summary = 'simple build test' + def run(self): + self.bs_sourcetree.init() + self.bs_sourcetree.configure() + self.bs_sourcetree.build() + self.bs_sourcetree.install() + + +class ConfigCommand(SourceClassCommand): + name = 'config' + summary = 'set/get config values' + usage = '(srcdir|builddir|installdir)' + + def validate_args(self, *args, **kwargs): + items = ('srcdir', 'builddir', 'installdir', ) + if len(args) == 0: + raise CommandLineError("'%s' requires at least one parameter (%s)", self.name, + ', '.join(items)) + elif len(args) == 1 and args[0] in items: + pass + elif len(args) == 2 and args[0] in items: + if args[0] in ('srcdir', ): + raise CommandLineError("'%s' command cannot change 'srcdir'", self.name) + else: + pass + else: + raise CommandLineError("'%s' requires less or different parameters", self.name) + + def run(self): + git_get_items = ('builddir', 'installdir', 'srcdir') + git_set_items = ('builddir', 'installdir', ) + if len(self.args) == 1: + if self.args[0] in git_get_items: + print getattr(self.vcs_sourcetree.config, self.args[0]) + else: + assert(False) + elif len(self.args) == 2: + if self.args[0] == 'builddir': + self.vcs_sourcetree.config.builddir = self.args[1] + elif self.args[0] == 'installdir': + self.vcs_sourcetree.config.installdir = self.args[1] + else: + assert(False) + else: + assert(False) + + +######################################################################## +# Commands +######################################################################## + +class NBB_Command(object): + def __init__(self, cmd, cmdargs, context): + if Command.plugins.has_key(cmd): + try: + c = Command.plugins[cmd](*cmdargs, **{'context':context}) + c.run() + except CommandLineError, e: + print "%(prog)s: Fatal:" % context, e + sys.exit(2) + except ProgramRunError, e: + print "%(prog)s: Fatal:" % context, e + print "Program aborted." + else: + print "Fatal: Unknown command '%s'" % cmd + raise NotImplementedError() + return + diff --git a/nbb/nbblib/main.py b/nbb/nbblib/main.py new file mode 100644 index 0000000..920a03f --- /dev/null +++ b/nbb/nbblib/main.py @@ -0,0 +1,203 @@ +######################################################################## +# Main program +######################################################################## + + +"""\ +nbb (ndim's branch builder) %(PACKAGE_VERSION)s +Build, install given branch of source code into a branch specific place +Copyright (C) 2007, 2008 Hans Ulrich Niedermann +License conditions TBA + +Usage: %(prog)s + +Features: + * supports git branches (TBD: git config) + * supports bzr branches (requires useful branch nick, TBD: bzr config) + * does out-of-source-tree builds (in-source-tree-builds unsupported) + * direct support for automake/autoconf based build systems + * TBD: supports execution of user commands in source, build, install dirs + +TODO: + * VCS config support ('git config', etc.) + * Build system support: automake/autoconf, cmake + * Design nice user interface. Requirements: + * print top_srcdir, builddir, installdir + * start subshell in top_srcdir, builddir, installdir + * run 'autoreconf' type step + * run 'configure' type step + * run 'make' type step + * run 'make install' type step + * run custom (make) commands + * Bash syntax completion for that user interface. + * Man page or something similar. + +Command line interface (TBD): + + Run default build commands: + $ %(prog)s [general options] init [command specific options] + $ %(prog)s [general options] configure [command specific options] + $ %(prog)s [general options] build [command specific options] + $ %(prog)s [general options] install [command specific options] + + Run cleanup commands: + TBD + + Get/set config: + $ %(prog)s [general options] config srcdir + $ %(prog)s [general options] config builddir [] + $ %(prog)s [general options] config installdir [] + + Start an interactive shell in either of the three directories: + $ %(prog)s [general options] src-sh [command specific options] + $ %(prog)s [general options] build-sh [command specific options] + $ %(prog)s [general options] install-sh [command specific options] + + Run command in builddir: + $ %(prog)s [general options] run [...] + $ %(prog)s [general options] run [command specific options... <-->] ... + + (Not sure about these) + Run a non-interactive shell command in either of the three directories: + $ %(prog)s [general options] src-sh [command specific options] ... + $ %(prog)s [general options] build-sh [command specific options] ... + $ %(prog)s [general options] install-sh [command specific options] + +Global options: + + -h --help Print this help text + -V --version Print program version number + + -n --dry-run Do not actually execute any commands + + -b --build-system Force buildsystem detection (%(buildsystems)s) + -v --vcs Force VCS detection (%(vcssystems)s) +""" + + +from nbblib.bs import * +from nbblib.commands import * +from nbblib.package import * +from nbblib.vcs import * + + +def print_help(context): + print __doc__ % context, + + +class Property(object): + def __init__(self, **kwargs): + if kwargs.has_key('default'): + self.default = kwargs['default'] + def __get__(self, instance, owner): + if hasattr(self, 'value'): + return self.value + elif hasattr(self, 'default'): + return self.default + else: + return None + def __set__(self, instance, value): + if hasattr(self, 'value'): + raise "Property cannot be set more than once" + elif not self.isvalid(value): + raise "Property cannot be set to invalid value '%s'" % value + else: + self.value = self.convert(value) + def __str__(self): + if hasattr(self, 'value'): + return self.value + else: + return '' + def isvalid(self, value): + return True + def convert(self, value): + return value + + +class ProgProperty(Property): + def convert(self, value): + prog = value + idx = prog.rfind('/') + if idx >= 0: + prog = prog[idx+1:] + return prog + + +class VCSProperty(Property): + def isvalid(self, value): + return (value in VCSourceTree.plugins.keys()) + + +class BSProperty(Property): + def isvalid(self, value): + return (value in BSSourceTree.plugins.keys()) + + +class BoolProperty(Property): + def __init__(self, default=False): + super(BoolProperty, self).__init__(default=default) + def isvalid(self, value): + return (value in (True, False)) + +class DryRunProperty(BoolProperty): + def __init__(self): + super(DryRunProperty, self).__init__(default=False) + + +class Context(object): + PACKAGE_VERSION = Property() + prog = ProgProperty() + vcs = VCSProperty() + bs = BSProperty() + dry_run = DryRunProperty() + verbose = BoolProperty() + vcssystems = Property() + buildsystems = Property() + + def __getitem__(self, key): + """emulate a dict() for the purpose of "%(prog)s" % context""" + return getattr(self, key) + + +def main(argv): + context = Context() + context.PACKAGE_VERSION = PACKAGE_VERSION + context.vcssystems = ", ".join(VCSourceTree.plugins.keys()) + context.buildsystems = ", ".join(BSSourceTree.plugins.keys()) + context.prog = argv[0] + + if len(argv) < 2: + print "Fatal: %(prog)s requires some arguments" % context + return 2 + + i = 1 + while i 1: + raise AmbigousVCSource(srcdir, matches.values()) + elif len(matches) < 1: + raise NotAVCSourceTree(srcdir) + return matches[matches.keys()[0]] + + def get_config(self): + """Get configuration object which determines builddir etc""" + return AbstractConfig(self.tree_root(), self.branch_name()) + config = property(get_config) + + def tree_root(self): + """Get absolute path to source tree root""" + raise NotImplementedError() + + def branch_name(self): + """Return name identifying the branch""" + raise NotImplementedError() + + def __str__(self): + return repr(self) + + def __repr__(self): + return "<%s(%s, %s)>" % (self.__class__.__name__, + repr(self.tree_root()), + repr(self.branch_name())) + + +######################################################################## +# VCS Source Tree plugins +######################################################################## + +class GitSourceTree(VCSourceTree): + + name = 'git' + + def __init__(self, srcdir, context): + super(GitSourceTree, self).__init__(context) + os.chdir(srcdir) + if "true" != prog_stdout(["git", "rev-parse", + "--is-inside-work-tree"]): + raise NotAVCSourceTree() + reldir = prog_stdout(["git", "rev-parse", "--show-cdup"]) + if reldir: + os.chdir(reldir) + self.__tree_root = os.getcwd() + + def get_config(self): + return GitConfig(self.tree_root(), self.branch_name()) + config = property(get_config) + + def tree_root(self): + return self.__tree_root + + def branch_name(self): + bname = prog_stdout(["git", "symbolic-ref", "HEAD"]) + refs,heads,branch = bname.split('/') + assert(refs=='refs' and heads=='heads') + return branch + + +class GitConfig(AbstractConfig): + """git config interface""" + + def __init__(self, *args, **kwargs): + super(GitConfig, self).__init__(*args, **kwargs) + + def _itemname(self, item): return '.'.join((GIT_CONFIG_PREFIX, item, )) + def _myreldir(self, rdir): + return os.path.join(self._srcdir, rdir, self._nick) + + def get_builddir(self): + ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('builddir')]) + assert(stderr == "") + if ret == 0 and stdout: + return self._myreldir(stdout) + else: + return super(GitConfig, self).get_builddir() + + def set_builddir(self, value): + ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('builddir'), value]) + assert(ret == 0 and stdout == "" and stderr == "") + + builddir = property(get_builddir, set_builddir) + + def get_installdir(self): + ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('installdir')]) + assert(stderr == "") + if ret == 0 and stdout: + return self._myreldir(stdout) + else: + return super(GitConfig, self).get_installdir() + + def set_installdir(self, value): + ret, stdout, stderr = prog_retstd(['git', 'config', self._itemname('installdir'), value]) + assert(ret == 0 and stdout == "" and stderr == "") + + installdir = property(get_installdir, set_installdir) + + +class BzrSourceTree(VCSourceTree): + + name = 'bzr' + + def __init__(self, srcdir, context): + super(BzrSourceTree, self).__init__(context) + try: + import bzrlib.workingtree + wt,b = bzrlib.workingtree.WorkingTree.open_containing(srcdir) + except bzrlib.errors.NotBranchError: + raise NotAVCSourceTree() + except ImportError: + raise NotAVCSourceTree() + self.wt = wt + #print "wt:", wt + #print "wt:", dir(wt) + #print "wt.branch:", wt.branch + #print "wt.branch:", dir(wt.branch) + #print "wt.branch.nick:", wt.branch.nick + #print "wt.branch.abspath:", wt.branch.abspath + #print "wt.branch.base:", wt.branch.base + #print "wt.branch.basis_tree:", wt.branch.basis_tree() + + def tree_root(self): + proto,host,path,some,thing = urlparse.urlsplit(self.wt.branch.base) + assert(proto == "file" and host == "") + assert(some == "" and thing == "") + return os.path.abspath(path) + + def branch_name(self): + return self.wt.branch.nick + + diff --git a/test/Makefile.am b/test/Makefile.am index 8a192b2..1961846 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -30,11 +30,15 @@ git-version-check: cd $(top_builddir) && $(MAKE) git-version-check check-local: git-version-check atconfig atlocal $(srcdir)/$(TESTSUITE) - $(SHELL) "$(srcdir)/$(TESTSUITE)" AUTOTEST_PATH=`cd $(top_builddir) > /dev/null && pwd`:`cd $(top_builddir)/nbb > /dev/null && pwd` \ + abs_top_builddir="$$(cd "$(top_builddir)" > /dev/null 2>&1 && pwd)"; \ + abs_top_srcdir="$$(cd "$(top_srcdir)" > /dev/null 2>&1 && pwd)"; \ + $(SHELL) "$(srcdir)/$(TESTSUITE)" AUTOTEST_PATH=`cd $(top_builddir)/nbb > /dev/null && pwd` \ + PYTHONPATH="$${abs_top_builddir}/nbb" \ $(TESTSUITEFLAGS) installcheck-local: atconfig atlocal $(srcdir)/$(TESTSUITE) $(SHELL) "$(srcdir)/$(TESTSUITE)" AUTOTEST_PATH='$(bindir)' \ + PYTHONPATH="$(DESTDIR)$(pythondir)" \ $(TESTSUITEFLAGS) clean-local: diff --git a/test/nbb-basic.at b/test/nbb-basic.at index ce2d3b1..3af2273 100644 --- a/test/nbb-basic.at +++ b/test/nbb-basic.at @@ -97,3 +97,7 @@ AT_CLEANUP() dnl =================================================================== dnl =================================================================== + +dnl Local Variables: +dnl mode: Autoconf +dnl End: -- cgit