summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorHans Ulrich Niedermann <hun@n-dimensional.de>2008-06-23 01:46:32 +0200
committerHans Ulrich Niedermann <hun@n-dimensional.de>2008-07-15 12:28:52 +0200
commit41a34022fc7ee1831d20d5e13c459c33fc001732 (patch)
tree510faa9b313fb6df6c50c09638a833fe62515eb7 /src
parent62819016f187acd945fad5b77efde78f88235555 (diff)
downloadnbb-41a34022fc7ee1831d20d5e13c459c33fc001732.tar.gz
nbb-41a34022fc7ee1831d20d5e13c459c33fc001732.tar.xz
nbb-41a34022fc7ee1831d20d5e13c459c33fc001732.zip
Rename nbb/ subdir to src/
Diffstat (limited to 'src')
-rw-r--r--src/Makefile-files64
-rw-r--r--src/nbb.in47
-rw-r--r--src/nbblib/__init__.py6
-rw-r--r--src/nbblib/bs.py126
-rw-r--r--src/nbblib/commands.py274
-rw-r--r--src/nbblib/main.py229
-rw-r--r--src/nbblib/package.in8
-rw-r--r--src/nbblib/plugins.py54
-rw-r--r--src/nbblib/progutils.py55
-rw-r--r--src/nbblib/vcs.py217
10 files changed, 1080 insertions, 0 deletions
diff --git a/src/Makefile-files b/src/Makefile-files
new file mode 100644
index 0000000..6707adc
--- /dev/null
+++ b/src/Makefile-files
@@ -0,0 +1,64 @@
+# -*- makefile -*-
+
+if HAVE_PYTHON
+
+nodist_nbblib_PYTHON += src/nbblib/package.py
+CLEANFILES += src/nbblib/package.py
+
+nbblib_PYTHON += src/nbblib/__init__.py
+nbblib_PYTHON += src/nbblib/bs.py
+nbblib_PYTHON += src/nbblib/commands.py
+nbblib_PYTHON += src/nbblib/main.py
+nbblib_PYTHON += src/nbblib/plugins.py
+nbblib_PYTHON += src/nbblib/progutils.py
+nbblib_PYTHON += src/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 += src/nbb
+CLEANFILES += src/nbb
+
+# We cannot create src/nbb from src/nbb.in in configure.ac/config.status.
+# pythondir is defined as ${something}foobar, and that needs expansion.
+EXTRA_DIST += src/nbb.in
+src/nbb: src/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)/src/nbb.in > src/nbb.new
+ @if test "x$$($(GREP) '@[a-zA-Z0-9_]\{1,\}@' src/nbb.new)" = "x"; then :; \
+ else \
+ echo "FATAL: Unsubstituted markers remain in src/nbb.new."; \
+ $(GREP) '@[a-zA-Z0-9_]\{1,\}@' src/nbb.new; \
+ exit 1; \
+ fi
+ @if test -f src/nbb && cmp src/nbb.new src/nbb; \
+ then rm -f src/nbb.new; \
+ else mv -f src/nbb.new src/nbb; echo "INFO: Updating src/nbb"; fi
+ @chmod +x src/nbb
+
+endif
+
+clean-local: clean-local-nbblib
+clean-local-nbblib:
+ rm -f src/nbblib/*.pyc
+ @top_srcdir="$$(cd "$(top_srcdir)" > /dev/null 2>&1 && pwd)"; \
+ top_builddir="$$(cd "$(top_builddir)" > /dev/null 2>&1 && pwd)"; \
+ if test "x$${top_srcdir}" = "x$${top_builddir}"; then :; else \
+ for f in $(nbblib_PYTHON); do \
+ echo rm -f "$(top_builddir)/$$f"; \
+ rm -f "$(top_builddir)/$$f"; \
+ done; \
+ fi
+
+# End of Makefile-files.
diff --git a/src/nbb.in b/src/nbb.in
new file mode 100644
index 0000000..ac2c2f0
--- /dev/null
+++ b/src/nbb.in
@@ -0,0 +1,47 @@
+#!@PYTHON@
+"""\
+nbb - ndim's branch builder
+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
+"""
+
+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 [
+ (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:
+ sys.path = orig_path
+ if not lib_found:
+ sys.stderr.write("nbb: Fatal: Could not load nbblib.\n")
+ sys.exit(3)
+ import nbblib.main
+ nbblib.main.main(sys.argv)
+
+# vim: syntax=python
+# Local Variables:
+# mode: python
+# End:
diff --git a/src/nbblib/__init__.py b/src/nbblib/__init__.py
new file mode 100644
index 0000000..d919ff1
--- /dev/null
+++ b/src/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/src/nbblib/bs.py b/src/nbblib/bs.py
new file mode 100644
index 0000000..cb498cb
--- /dev/null
+++ b/src/nbblib/bs.py
@@ -0,0 +1,126 @@
+########################################################################
+# Buildsystem Source Tree plugins
+########################################################################
+
+
+import os
+from nbblib.plugins import *
+from nbblib.progutils 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')]
+ alist.extend(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)
+ if not os.path.exists(os.path.join(builddir, 'configure')):
+ self.init
+ os.chdir(builddir)
+ prog_run(["%s/configure" % self.config.srcdir,
+ "--prefix=%s" % self.config.installdir
+ ], self.context)
+
+ def build(self):
+ """'make'"""
+ builddir = self.config.builddir
+ if not os.path.exists(os.path.join(builddir, 'config.status')):
+ self.configure()
+ os.chdir(builddir)
+ prog_run(["make", ], self.context)
+
+ def install(self):
+ """'make install'"""
+ builddir = self.config.builddir
+ if not os.path.exists(os.path.join(builddir, 'config.status')):
+ self.configure()
+ os.chdir(builddir)
+ prog_run(["make", "install", "INSTALL=/usr/bin/install -p"],
+ self.context)
+
+
diff --git a/src/nbblib/commands.py b/src/nbblib/commands.py
new file mode 100644
index 0000000..45e3a61
--- /dev/null
+++ b/src/nbblib/commands.py
@@ -0,0 +1,274 @@
+import os
+import sys
+
+
+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 InitCommand(SourceClassCommand):
+ name = 'init'
+ summary = 'initialize buildsystem'
+ def run(self):
+ self.bs_sourcetree.init()
+
+class ConfigureCommand(SourceClassCommand):
+ name = 'configure'
+ summary = 'configure buildsystem'
+ def run(self):
+ self.bs_sourcetree.configure()
+
+class BuildCommand(SourceClassCommand):
+ name = 'build'
+ summary = 'build from source'
+ def run(self):
+ self.bs_sourcetree.build()
+
+class InstallCommand(SourceClassCommand):
+ name = 'install'
+ summary = 'install the built things'
+ def run(self):
+ self.bs_sourcetree.install()
+
+
+class MakeCommand(SourceClassCommand):
+ name = 'make'
+ summary = 'run make in builddir'
+ def validate_args(self, *args, **kwargs):
+ pass
+ def run(self):
+ os.chdir(self.bs_sourcetree.config.builddir)
+ prog_run(["make"] + list(self.args),
+ self.context)
+
+
+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' command 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()
+
diff --git a/src/nbblib/main.py b/src/nbblib/main.py
new file mode 100644
index 0000000..3014355
--- /dev/null
+++ b/src/nbblib/main.py
@@ -0,0 +1,229 @@
+########################################################################
+# 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>
+TBA: License conditions
+
+Usage: %(prog)s <to-be-determined>
+
+Features:
+ * supports git branches
+ * 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
+
+DONE:
+ * VCS config support ('git config', etc.)
+ * Build system support: automake/autoconf
+
+TODO: (Large list)
+ * Build system support: cmake, scons, ...
+ * Fine-tune init, configure, build, install commands with knowledge
+ gained with git-amb, especially the command interdependencies.
+ * implement *-sh and *-run commands
+ * General removal of redundancy in Python code.
+ * More declarative syntax elements in the Python code.
+ * Use declarations for command line parsing, and help text generation.
+ * Add global --nick or similar option to determine the branch
+ name to use for composing the pathes.
+ * Store config in ${srcdir}/.nbb.conf instead of 'git config'?
+ More portable. bzr does not have a config interface, for example.
+ * Model different "stages" of e.g. automake builds as distinct objects,
+ including proper dependency detectors, and stuff? OK, we're not going
+ to duplicate scons here.
+ * Design nice user interface. Requirements:
+ * print top_srcdir, builddir, installdir. OK: 'config'
+ * start subshell in top_srcdir, builddir, installdir
+ * run 'autoreconf' type step. OK: 'init'
+ * run 'configure' type step. OK: 'configure'
+ * run 'make' type step. OK: 'build'
+ * run 'make install' type step. OK: 'install'
+ * run custom (make) commands. OK: 'make'
+ * Bash syntax completion for that user interface.
+ * Man page or something similar. Generate from help texts?
+
+TBD: Command line interface:
+
+ 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:
+ $ %(prog)s [general options] purge [command specific options]
+ Command specific options:
+ --builddir-only
+ --installdir-only
+ $ %(prog)s [general options] purge-all # either this
+ $ %(prog)s [general options] purge --all # or that
+ TBD: 'make clean', 'make distclean' and similar stuff?
+
+ 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_version(context):
+ print "%(prog)s (ndim's branch builder) %(PACKAGE_VERSION)s" % context
+
+
+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_version(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/src/nbblib/package.in b/src/nbblib/package.in
new file mode 100644
index 0000000..240bfd6
--- /dev/null
+++ b/src/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/src/nbblib/plugins.py b/src/nbblib/plugins.py
new file mode 100644
index 0000000..bd6839b
--- /dev/null
+++ b/src/nbblib/plugins.py
@@ -0,0 +1,54 @@
+class DuplicatePluginName(Exception):
+ pass
+
+
+class PluginDict(object):
+ """Helper for GenericPluginMeta class
+
+ Behaves basically like a standard dict, but will raise an exception
+ 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/src/nbblib/progutils.py b/src/nbblib/progutils.py
new file mode 100644
index 0000000..456405e
--- /dev/null
+++ b/src/nbblib/progutils.py
@@ -0,0 +1,55 @@
+########################################################################
+# Utility functions
+########################################################################
+
+
+import os
+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/src/nbblib/vcs.py b/src/nbblib/vcs.py
new file mode 100644
index 0000000..b0aee5b
--- /dev/null
+++ b/src/nbblib/vcs.py
@@ -0,0 +1,217 @@
+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
+
+