summaryrefslogtreecommitdiffstats
path: root/nbb/nbb_lib.in
diff options
context:
space:
mode:
Diffstat (limited to 'nbb/nbb_lib.in')
-rw-r--r--nbb/nbb_lib.in584
1 files changed, 584 insertions, 0 deletions
diff --git a/nbb/nbb_lib.in b/nbb/nbb_lib.in
new file mode 100644
index 0000000..17e2aa1
--- /dev/null
+++ b/nbb/nbb_lib.in
@@ -0,0 +1,584 @@
+"""\
+nbb (ndim's branch builder) from @PACKAGE_NAME@ @PACKAGE_VERSION@
+Build, install given branch of source code into a branch specific place
+Copyright (C) 2007,2008 Hans Ulrich Niedermann
+
+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
+version = "@PACKAGE_VERSION@"
+
+
+########################################################################
+# Utility functions
+########################################################################
+
+
+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()
+
+
+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):
+ """Run program showing its output. Raise exception if retcode != 0."""
+ print "RUN:", call_list
+ print " in", os.getcwd()
+ # 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):
+ self.srcdir = srcdir
+ self.nick = nick
+ def srcdir(self):
+ return os.path.join(self.srcdir)
+ def builddir(self):
+ return os.path.join(self.srcdir, "_build", self.nick)
+ def installdir(self):
+ return os.path.join(self.srcdir, "_install", self.nick)
+
+
+class CommandLineError(Exception):
+ def __init__(self, message, *args):
+ 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 AmbigousAVCSource(Exception):
+ def __init__(self, srcdir, matches):
+ super(AmbigousAVCSource, self).__init__()
+ self.srcdir = srcdir
+ self.matches = matches
+ def __str__(self):
+ return ("More than one source tree VCS type detected for '%s': %s"
+ % (self.srcdir, self.matches))
+
+
+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 detect(cls, srcdir):
+ """Detect VCS tree type and return object representing it"""
+ if len(VCSourceTree.plugins) < 1:
+ raise "No VC source tree classes registered"
+ matches = PluginDict()
+ print "VCSourceTree.detect", VCSourceTree.plugins
+ print "srcdir", srcdir
+ for key, klass in VCSourceTree.plugins.items():
+ print "key", key
+ print "klass", klass
+ try:
+ matches[key] = klass(srcdir)
+ print "MATCH:", srcdir
+ except NotAVCSourceTree, e:
+ pass
+ if len(matches) > 1:
+ raise AmbigousAVCSource(srcdir, str(matches.values()))
+ elif len(matches) < 1:
+ raise NotAVCSourceTree(srcdir)
+ print "Matches:", matches
+ return matches[matches.keys()[0]]
+ detect = classmethod(detect)
+
+ def get_config(self):
+ """Get configuration object which determines builddir etc"""
+ return AbstractConfig(self.tree_root(), self.branch_name())
+
+ 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 "VCS-Source-Tree(%s, %s, %s)" % (self.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
+
+ 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
+
+ def __init__(self, *args, **kwargs):
+ self.validate_args(*args, **kwargs)
+ self.args = args
+ self.kwargs = kwargs
+
+ 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):
+ name = 'help'
+ summary = 'print help text'
+ def run(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)
+
+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)
+ srcdir = os.getcwd()
+ absdir = os.path.abspath(srcdir)
+ self.vcs_sourcetree = VCSourceTree.detect(absdir)
+ print "vcs_sourcetree", str(self.vcs_sourcetree)
+ assert(self.vcs_sourcetree)
+ self.bs_sourcetree = BSSourceTree.detect(self.vcs_sourcetree)
+ print "bs_sourcetree", str(self.bs_sourcetree)
+
+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()
+
+
+########################################################################
+# VCS Source Tree plugins
+########################################################################
+
+class GitSourceTree(VCSourceTree):
+
+ name = 'git'
+
+ def __init__(self, srcdir):
+ 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 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):
+ 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 path
+
+ def branch_name(self):
+ return self.wt.branch.nick
+
+
+########################################################################
+# Buildsystem Source Tree plugins
+########################################################################
+
+class NotABSSourceTree(Exception): pass
+
+
+class BSSourceTree(object):
+ __metaclass__ = GenericPluginMeta
+
+ def detect(cls, vcs_tree):
+ """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)
+ matches[key] = t
+ except NotABSSourceTree:
+ 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 "Source tree type for '%s' not detected" % (vcs_tree,)
+ print "Matches:", matches
+ return matches[matches.keys()[0]]
+ detect = classmethod(detect)
+
+ 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):
+ srcdir = vcs_tree.tree_root()
+ self.config = vcs_tree.get_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()
+ self.srcdir = srcdir
+
+ def tree_root(self):
+ return self.srcdir
+
+ def init(self):
+ """'autoreconf'"""
+ prog_run(["autoreconf", "-v", "-i", "-s", self.srcdir])
+
+ 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.srcdir,
+ "--prefix=%s" % self.config.installdir()
+ ])
+
+ def build(self):
+ """'make'"""
+ os.chdir(self.config.builddir())
+ prog_run(["make", ])
+
+ def install(self):
+ """'make install'"""
+ os.chdir(self.config.builddir())
+ prog_run(["make", "install", "INSTALL=/usr/bin/install -p"])
+
+
+########################################################################
+# Commands
+########################################################################
+
+class NBB_Command(object):
+ def __init__(self, cmd, cmdargs):
+ if Command.plugins.has_key(cmd):
+ try:
+ c = Command.plugins[cmd](*cmdargs)
+ 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())
+
+
+def print_help():
+ print __doc__ % outdict,
+
+
+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
+
+ verbosity = 0
+ i = 1
+ 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 (@PACKAGE_NAME@) @PACKAGE_VERSION@" % outdict
+ return
+ elif argv[i] in ('-v', '--verbose'):
+ verbosity = verbosity + 1
+ elif argv[i] in ('-q', '--quiet'):
+ verbosity = verbosity - 1
+ print "", i, argv[i]
+ i = i + 1
+ cmd = argv[i]
+ cmdargs = argv[i+1:]
+ nbb = NBB_Command(cmd, cmdargs)
+
+
+if __name__ == '__main__':
+ raise "This is not a library"
+
+# vim: syntax=python
+# Local Variables:
+# mode: python
+# End: