summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Ulrich Niedermann <hun@n-dimensional.de>2008-06-22 16:04:13 +0200
committerHans Ulrich Niedermann <hun@n-dimensional.de>2008-07-15 12:28:51 +0200
commitad6d1b84001a051e76d9c1ca516997e71750287a (patch)
treef910143973745ed3d31d76029af86fc512e0fe5f
parent5fcdc5fb90e289ea9739a219fc77e4222eff0afb (diff)
downloadnbb-ad6d1b84001a051e76d9c1ca516997e71750287a.tar.gz
nbb-ad6d1b84001a051e76d9c1ca516997e71750287a.tar.xz
nbb-ad6d1b84001a051e76d9c1ca516997e71750287a.zip
Split nbblib into multiple source files
This necessitated - fixing nbb/nbb.in -> nbb/nbb substitution - meddling with PYTHONPATH - other fixes
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am5
-rw-r--r--configure.ac3
-rw-r--r--nbb/Makefile-files50
-rw-r--r--nbb/nbb.in24
-rw-r--r--nbb/nbblib/__init__.in854
-rw-r--r--nbb/nbblib/__init__.py6
-rw-r--r--nbb/nbblib/bs.py113
-rw-r--r--nbb/nbblib/commands.py234
-rw-r--r--nbb/nbblib/main.py203
-rw-r--r--nbb/nbblib/package.in8
-rw-r--r--nbb/nbblib/plugins.py55
-rw-r--r--nbb/nbblib/progutils.py54
-rw-r--r--nbb/nbblib/vcs.py216
-rw-r--r--test/Makefile.am6
-rw-r--r--test/nbb-basic.at4
16 files changed, 960 insertions, 876 deletions
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 <hun@n-dimensional.de>
-License conditions TBA
-
-Usage: %(prog)s <to-be-determined>
-
-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 [<builddir>]
- $ %(prog)s [general options] config installdir [<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 <command> [<param>...]
- $ %(prog)s [general options] run [command specific options... <-->] <cmd>...
-
- (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] <command>...
- $ %(prog)s [general options] build-sh [command specific options] <command>...
- $ %(prog)s [general options] install-sh [command specific options] <cmd...>
-
-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 <command> is given, print the help for <command>.
- Else, print a list of commands and general help.
- """
-
- name = 'help'
- summary = 'print help text'
- usage = '[<command>]'
-
- 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 '<undefined property>'
- 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<len(argv):
- if argv[i][0] != '-':
- break
- if argv[i] in ('-h', '--help'):
- print_help()
- return
- elif argv[i] in ('-V', '--version'):
- print "%(prog)s (ndim's branch builder) %(PACKAGE_VERSION)s" % outdict
- return
- elif argv[i] in ('-n', '--dry-run'):
- ctx.dry_run = True
- elif argv[i] in ('-b', '--build-system'):
- i = i + 1
- assert(i<len(argv))
- ctx.bs = argv[i]
- elif argv[i][:6] == '--build-system=':
- ctx.bs = argv[i][6:]
- elif argv[i] in ('-v', '--vcs'):
- i = i + 1
- assert(i<len(argv))
- ctx.vcs = argv[i]
- elif argv[i][:6] == '--vcs=':
- ctx.vcs = argv[i][6:]
- elif argv[i] in ('--verbose', ):
- ctx.verbose = True
- # print "", i, argv[i]
- i = i + 1
- cmd = argv[i]
- cmdargs = argv[i+1:]
- nbb = NBB_Command(cmd, cmdargs, context=ctx)
-
-
-if __name__ == '__main__':
- raise "This is not a library"
-
-# vim: syntax=python
-# Local Variables:
-# mode: python
-# End:
diff --git a/nbb/nbblib/__init__.py b/nbb/nbblib/__init__.py
new file mode 100644
index 0000000..d919ff1
--- /dev/null
+++ b/nbb/nbblib/__init__.py
@@ -0,0 +1,6 @@
+from nbblib.bs import *
+from nbblib.commands import *
+from nbblib.package import *
+from nbblib.plugins import *
+from nbblib.vcs import *
+from nbblib.bs import *
diff --git a/nbb/nbblib/bs.py b/nbb/nbblib/bs.py
new file mode 100644
index 0000000..a254c95
--- /dev/null
+++ b/nbb/nbblib/bs.py
@@ -0,0 +1,113 @@
+########################################################################
+# Buildsystem Source Tree plugins
+########################################################################
+
+
+import os
+from nbblib.plugins import *
+
+
+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)
+
+
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 <command> is given, print the help for <command>.
+ Else, print a list of commands and general help.
+ """
+
+ name = 'help'
+ summary = 'print help text'
+ usage = '[<command>]'
+
+ 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 <hun@n-dimensional.de>
+License conditions TBA
+
+Usage: %(prog)s <to-be-determined>
+
+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 [<builddir>]
+ $ %(prog)s [general options] config installdir [<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 <command> [<param>...]
+ $ %(prog)s [general options] run [command specific options... <-->] <cmd>...
+
+ (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] <command>...
+ $ %(prog)s [general options] build-sh [command specific options] <command>...
+ $ %(prog)s [general options] install-sh [command specific options] <cmd...>
+
+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 '<undefined property>'
+ 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<len(argv):
+ if argv[i][0] != '-':
+ break
+ if argv[i] in ('-h', '--help'):
+ print_help(context)
+ return
+ elif argv[i] in ('-V', '--version'):
+ print "%(prog)s (ndim's branch builder) %(PACKAGE_VERSION)s" % context
+ return
+ elif argv[i] in ('-n', '--dry-run'):
+ context.dry_run = True
+ elif argv[i] in ('-b', '--build-system'):
+ i = i + 1
+ assert(i<len(argv))
+ context.bs = argv[i]
+ elif argv[i][:6] == '--build-system=':
+ context.bs = argv[i][6:]
+ elif argv[i] in ('-v', '--vcs'):
+ i = i + 1
+ assert(i<len(argv))
+ context.vcs = argv[i]
+ elif argv[i][:6] == '--vcs=':
+ context.vcs = argv[i][6:]
+ elif argv[i] in ('--verbose', ):
+ context.verbose = True
+ # print "", i, argv[i]
+ i = i + 1
+ cmd = argv[i]
+ cmdargs = argv[i+1:]
+ nbb = NBB_Command(cmd, cmdargs, context=context)
diff --git a/nbb/nbblib/package.in b/nbb/nbblib/package.in
new file mode 100644
index 0000000..240bfd6
--- /dev/null
+++ b/nbb/nbblib/package.in
@@ -0,0 +1,8 @@
+# Used to make sure nbb_lib and nbb fit together
+PACKAGE_VERSION = "@PACKAGE_VERSION@"
+GIT_CONFIG_PREFIX = 'nbb'
+
+# vim: syntax=python
+# Local Variables:
+# mode: python
+# End:
diff --git a/nbb/nbblib/plugins.py b/nbb/nbblib/plugins.py
new file mode 100644
index 0000000..64633f6
--- /dev/null
+++ b/nbb/nbblib/plugins.py
@@ -0,0 +1,55 @@
+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
+
+
diff --git a/nbb/nbblib/progutils.py b/nbb/nbblib/progutils.py
new file mode 100644
index 0000000..5807d76
--- /dev/null
+++ b/nbb/nbblib/progutils.py
@@ -0,0 +1,54 @@
+########################################################################
+# Utility functions
+########################################################################
+
+
+import subprocess
+
+
+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
+
diff --git a/nbb/nbblib/vcs.py b/nbb/nbblib/vcs.py
new file mode 100644
index 0000000..164077b
--- /dev/null
+++ b/nbb/nbblib/vcs.py
@@ -0,0 +1,216 @@
+import os
+import urlparse
+
+
+from nbblib.package import *
+from nbblib.plugins import *
+from nbblib.progutils import *
+
+
+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)
+
+
+########################################################################
+# 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()))
+
+
+########################################################################
+# 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: