From f41bcd76c51161593fc329daa6d5c11aafd44c03 Mon Sep 17 00:00:00 2001 From: Hans Ulrich Niedermann Date: Thu, 26 Jun 2008 18:31:53 +0200 Subject: Unified new plugin architecture Changes required on the way: - New nbb commands detect-vcs and detect-bs including tests. - misc small fixes --- src/Makefile-files | 1 + src/nbb.in | 14 ++++-- src/nbblib/__init__.py | 25 ++++++---- src/nbblib/bs.py | 119 ++++++++++++++++++++++++----------------------- src/nbblib/commands.py | 82 +++++++++++++++++++++++--------- src/nbblib/main.py | 17 +++++-- src/nbblib/newplugins.py | 37 ++++++++++----- src/nbblib/vcs.py | 88 ++++++++++++++++++++--------------- 8 files changed, 237 insertions(+), 146 deletions(-) (limited to 'src') diff --git a/src/Makefile-files b/src/Makefile-files index 6707adc..0a93287 100644 --- a/src/Makefile-files +++ b/src/Makefile-files @@ -10,6 +10,7 @@ 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/newplugins.py nbblib_PYTHON += src/nbblib/progutils.py nbblib_PYTHON += src/nbblib/vcs.py diff --git a/src/nbb.in b/src/nbb.in index ac3b00e..940c519 100644 --- a/src/nbb.in +++ b/src/nbb.in @@ -18,8 +18,9 @@ import logging # since python 2.3 # funcName: Since python 2.5 # format="%(filename)s:%(lineno)d:%(funcName)s:" -logging.basicConfig(level = logging.DEBUG, - format = "%(levelname)s: %(message)s", +logging.basicConfig(format = "%(levelname)s: %(message)s", + # level = logging.DEBUG, + level = logging.WARNING, stream = sys.stderr) if False: logging.debug("xxx debug") @@ -42,17 +43,20 @@ if __name__ == '__main__': sys.path = path try: import nbblib - #print "nbblib.PACKAGE_VERSION", nbblib.PACKAGE_VERSION - #print "PACKAGE_VERSION", PACKAGE_VERSION + logging.debug("nbb.PACKAGE_VERSION %s, nbblib.PACKAGE_VERSION %s", + PACKAGE_VERSION, nbblib.PACKAGE_VERSION) assert(nbblib.PACKAGE_VERSION == PACKAGE_VERSION) lib_found = True break except AssertionError, e: + logging.debug("Assertion error", exc_info=True) sys.path = orig_path except ImportError, e: + logging.debug("Import error", exc_info=True) sys.path = orig_path if not lib_found: - sys.stderr.write("nbb: Fatal: Could not load nbblib.\n") + logging.error("nbb: Fatal: Could not load nbblib.") + logging.shutdown() sys.exit(3) import nbblib.main nbblib.main.cmdmain(sys.argv) diff --git a/src/nbblib/__init__.py b/src/nbblib/__init__.py index d1632a8..95fe6da 100644 --- a/src/nbblib/__init__.py +++ b/src/nbblib/__init__.py @@ -1,9 +1,18 @@ -from nbblib.bs import * -from nbblib.commands import * -from nbblib.package import * -from nbblib.plugins import * -from nbblib.vcs import * -from nbblib.bs import * +#from nbblib.bs import * +#from nbblib.commands import * +#from nbblib.package import * +#from nbblib.plugins import * +#from nbblib.vcs import * -for submodule in ('newplugins', ): - setattr(__module__, submodule, __import__("nbblib.%s" % submodule)) +import nbblib.bs as bs +import nbblib.commands as commands +import nbblib.newplugins as newplugins +import nbblib.package as package +import nbblib.plugins as plugins +import nbblib.vcs as plugins + +from package import PACKAGE_VERSION + +__all__ = ['bs', 'commands', 'newplugins', + 'package', 'plugins', 'vcs', + 'PACKAGE_VERSION'] diff --git a/src/nbblib/bs.py b/src/nbblib/bs.py index 70f4ee0..8509c92 100644 --- a/src/nbblib/bs.py +++ b/src/nbblib/bs.py @@ -6,69 +6,53 @@ import os import logging -from nbblib.plugins import * -from nbblib.progutils import * +from nbblib import progutils +from nbblib import newplugins as plugins -class NotABSSourceTree(Exception): + +class NotABSSourceTree(plugins.PluginNoMatch): 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,)) + return "Unknown BS source tree type: %s" % repr(self.vcs_tree.tree_root) -class AmbigousBSSource(Exception): - def __init__(self, srcdir, matches): - super(AmbigousBSSource, self).__init__() - self.srcdir = srcdir - self.matches = matches +class AmbigousBSDetection(plugins.AmbigousPluginDetection): + def __init__(self, matches, cls, context, vcs_tree): + super(AmbigousBSDetection, self).__init__(matches, cls, context) + self.vcs_tree = vcs_tree 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))) + alist = self.matches.keys() + alist.sort() + return "Ambigous BS types detected for %s:\n %s" % (repr(self.vcs_tree.tree_root), + '\n '.join(alist)) -class BSSourceTree(object): - __metaclass__ = GenericPluginMeta - - def __init__(self, context): - super(BSSourceTree, self).__init__() - self.context = context +class BSSourceTree(plugins.GenericDetectPlugin): + __metaclass__ = plugins.GenericPluginMeta + no_match_exception = NotABSSourceTree + ambigous_match_exception = AmbigousBSDetection @classmethod - def detect(cls, vcs_tree, context): - """Find BS tree type and return it""" - if len(cls.plugins) < 1: - raise NoPluginsRegistered(cls) - #logging.debug("CLASS %s", cls) - matches = PluginDict() - for key, klass in cls.plugins.iteritems(): - try: - t = klass(vcs_tree, context) - if t.tree_root() == vcs_tree.tree_root(): - #logging.debug("KLASS %s", klass) - 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([str(x) for x in matches]))) - elif len(matches) < 1: - raise NotABSSourceTree(vcs_tree) - return matches[matches.keys()[0]] + def validate(cls, obj, vcs_tree): + logging.debug("BSSourceTree.validate(%s, %s, %s) %s %s", + cls, obj, vcs_tree, + repr(obj.tree_root), repr(vcs_tree.tree_root)) + return obj.tree_root == vcs_tree.tree_root + + + def get_tree_root(self): return self._get_tree_root() + tree_root = property(get_tree_root) + def __str__(self): return "BS-Source-Tree(%s, %s)" % (self.name, - repr(self.tree_root())) + repr(self.tree_root)) # Abstract methods - def tree_root(self): raise NotImplementedError() + def _get_tree_root(self): raise NotImplementedError() def init(self): raise NotImplementedError() def configure(self): raise NotImplementedError() def build(self): raise NotImplementedError() @@ -77,9 +61,9 @@ class BSSourceTree(object): class AutomakeSourceTree(BSSourceTree): name = 'automake' - def __init__(self, vcs_tree, context): + def __init__(self, context, vcs_tree): super(AutomakeSourceTree, self).__init__(context) - srcdir = vcs_tree.tree_root() + srcdir = vcs_tree.tree_root self.config = vcs_tree.config flag = False for f in [ os.path.join(srcdir, 'configure.ac'), @@ -89,15 +73,15 @@ class AutomakeSourceTree(BSSourceTree): flag = True break if not flag: - raise NotABSSourceTree(vcs_tree) + raise self.no_match_exception(vcs_tree) - def tree_root(self): + def _get_tree_root(self): return self.config.srcdir def init(self): """'autoreconf'""" - prog_run(["autoreconf", "-v", "-i", "-s", self.config.srcdir], - self.context) + progutils.prog_run(["autoreconf", "-v", "-i", "-s", self.config.srcdir], + self.context) def configure(self): """'configure --prefix'""" @@ -106,10 +90,10 @@ class AutomakeSourceTree(BSSourceTree): 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, - "--enable-maintainer-mode", - ], self.context) + progutils.prog_run(["%s/configure" % self.config.srcdir, + "--prefix=%s" % self.config.installdir, + "--enable-maintainer-mode", + ], self.context) def build(self): """'make'""" @@ -117,7 +101,7 @@ class AutomakeSourceTree(BSSourceTree): if not os.path.exists(os.path.join(builddir, 'config.status')): self.configure() os.chdir(builddir) - prog_run(["make", ], self.context) + progutils.prog_run(["make", ], self.context) def install(self): """'make install'""" @@ -125,7 +109,26 @@ class AutomakeSourceTree(BSSourceTree): 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) + progutils.prog_run(["make", "install", "INSTALL=/usr/bin/install -p"], + self.context) + + +class SconsSourceTree(BSSourceTree): + name = 'scons' + def __init__(self, context, vcs_tree): + super(SconsSourceTree, self).__init__(context) + srcdir = vcs_tree.tree_root + self.config = vcs_tree.config + flag = False + for f in [ os.path.join(srcdir, 'SConstruct'), + ]: + if os.path.exists(f): + flag = True + break + if not flag: + raise self.no_match_exception(vcs_tree) + self.__tree_root = srcdir + def _get_tree_root(self): + return self.__tree_root diff --git a/src/nbblib/commands.py b/src/nbblib/commands.py index eaf32b6..b238b49 100644 --- a/src/nbblib/commands.py +++ b/src/nbblib/commands.py @@ -1,12 +1,13 @@ import os import sys +import logging -from nbblib.package import * -from nbblib.plugins import * -from nbblib.progutils import * -from nbblib.vcs import * -from nbblib.bs import * +import nbblib.package as package +import nbblib.newplugins as plugins +import nbblib.progutils as progutils +import nbblib.vcs as vcs +import nbblib.bs as bs def adjust_doc(doc): @@ -65,7 +66,7 @@ class Command(object): *args are the arguments from the command line **kwargs are additional parameters from within the program """ - __metaclass__ = GenericPluginMeta + __metaclass__ = plugins.GenericPluginMeta usage = '' @@ -141,30 +142,67 @@ 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 "Source tree types:", ", ".join(vcs.VCSourceTree.plugins.keys()) + print "Build system types:", ", ".join(bs.BSSourceTree.plugins.keys()) print "Commands:", ", ".join(Command.plugins.keys()) class SourceClassCommand(Command): """Base class for commands acting on source trees""" + name = None # abstract command class 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(context, absdir) - if context.verbose: - print "vcs_sourcetree", str(self.vcs_sourcetree) - assert(self.vcs_sourcetree) + + self.vcs_sourcetree = vcs.VCSourceTree.detect(context, absdir) + logging.debug("vcs_sourcetree %s", self.vcs_sourcetree) + + self.bs_sourcetree = bs.BSSourceTree.detect(context, + self.vcs_sourcetree) + logging.debug("bs_sourcetree %s", self.bs_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 + for x in ('srcdir', 'builddir', 'installdir'): + logging.info("CONFIG %s %s", x, getattr(cfg, x)) + + +class DetectCommand(Command): + name = None + def __init__(self, *args, **kwargs): + super(DetectCommand, self).__init__(*args, **kwargs) + self.context = kwargs['context'] + self.srcdir = os.getcwd() + self.absdir = os.path.abspath(self.srcdir) + def validate_args(self, *args, **kwargs): + pass + + +class DetectVCSCommand(DetectCommand): + name = "detect-vcs" + summary = "detect source tree VCS" + def __init__(self, *args, **kwargs): + super(DetectVCSCommand, self).__init__(*args, **kwargs) + self.vcs_sourcetree = vcs.VCSourceTree.detect(self.context, self.absdir) + logging.debug("vcs_sourcetree %s", self.vcs_sourcetree) + def run(self): + print 'VCS:', self.vcs_sourcetree.name, self.vcs_sourcetree.tree_root + + +class DetectBSCommand(DetectCommand): + name = "detect-bs" + summary = "detect source tree BS" + def __init__(self, *args, **kwargs): + super(DetectBSCommand, self).__init__(*args, **kwargs) + self.vcs_sourcetree = vcs.VCSourceTree.detect(self.context, self.absdir) + logging.debug("vcs_sourcetree %s", self.vcs_sourcetree) + self.bs_sourcetree = bs.BSSourceTree.detect(self.context, + self.vcs_sourcetree) + logging.debug("bs_sourcetree %s", self.bs_sourcetree) + def run(self): + print 'BS:', self.bs_sourcetree.name, self.bs_sourcetree.tree_root class BuildTestCommand(SourceClassCommand): @@ -209,8 +247,8 @@ class MakeCommand(SourceClassCommand): pass def run(self): os.chdir(self.bs_sourcetree.config.builddir) - prog_run(["make"] + list(self.args), - self.context) + progutils.prog_run(["make"] + list(self.args), + self.context) class ConfigCommand(SourceClassCommand): @@ -267,7 +305,7 @@ class NBB_Command(object): except CommandLineError, e: print "%(prog)s: Fatal:" % context, e sys.exit(2) - except ProgramRunError, e: + except progutils.ProgramRunError, e: print "%(prog)s: Fatal:" % context, e print "Program aborted." else: diff --git a/src/nbblib/main.py b/src/nbblib/main.py index f3d72cc..f2183a6 100644 --- a/src/nbblib/main.py +++ b/src/nbblib/main.py @@ -55,6 +55,7 @@ TODO: (Large list) * 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. + * BS autodetection might discover more than one BS instance of the same type? * Design nice user interface. Requirements: * print top_srcdir, builddir, installdir. OK: 'config' * start subshell in top_srcdir, builddir, installdir @@ -123,6 +124,8 @@ from nbblib.commands import * from nbblib.package import * from nbblib.vcs import * +import nbblib.newplugins as plugins + def print_version(context): print "%(prog)s (ndim's branch builder) %(PACKAGE_VERSION)s" % context @@ -130,8 +133,8 @@ def print_version(context): def print_help(context): print __doc__ % context, - - + + class PropertySetAgainError(Exception): def __str__(self): return "Property cannot be set more than once" @@ -143,7 +146,7 @@ class InvalidPropertyValue(Exception): self.value = value def __str__(self): return "Property cannot be set to invalid value '%s'" % self.value - + class Property(object): def __init__(self, **kwargs): @@ -269,6 +272,14 @@ def cmdmain(argv): try: main(argv) logging.shutdown() + except plugins.PluginNoMatch, e: + logging.shutdown() + print e + sys.exit(1) + except plugins.AmbigousPluginDetection, e: + logging.shutdown() + print e + sys.exit(1) except CommandLineError, e: logging.shutdown() print e diff --git a/src/nbblib/newplugins.py b/src/nbblib/newplugins.py index 06905fe..ee59a48 100644 --- a/src/nbblib/newplugins.py +++ b/src/nbblib/newplugins.py @@ -1,7 +1,21 @@ -"""Usage of newplugins module: +"""\ +newplugins.py - generic plugin system + +Basic plugin architecture (metaclass tricks) by Marty Alchin from +http://gulopine.gamemusic.org/2008/jan/10/simple-plugin-framework/ + +GenericPluginMeta slightly modified to + - store plugins as dict + - support plugin class hierarchies +Extended by GenericDetectPlugin to + - support auto-detection of the adequate plugin + +Example usage of the newplugins module: np = __import__(newplugins) +Example non-auto-detection plugin: + class NonDetectPluginType(object): __metaclass__ = np.GenericPluginMeta @@ -10,8 +24,16 @@ class PluginA(NonDetectPluginType): class PluginB(NonDetectPluginType): name = "PB" +Example auto-detection plugin: + class MyPluginType(np.GenericDetectPlugin): __metaclass__ = np.GenericPluginMeta + + # The calling convention for constructor + # detect(context, ) + # is defined here, and the same is used + # to construct the exceptions. + [no_match_exception = ...] [ambigous_match_exception = ...] [ @@ -25,13 +47,13 @@ class MyPluginA(MyPluginType): def __init__(self, context): super(MyPluginA, self).__init__(self, context) if not some_detection_successful: - raise np.PluginNoMatch() + raise self.no_match_exception() class MyPluginB(MyPluginType): name = "MB" def __init__(self, context): super(MyPluginB, self).__init__(self, context) if not other_detection_successful: - raise np.PluginNoMatch() + raise self.no_match_exception() """ @@ -94,15 +116,6 @@ class PluginDict(dict): super(PluginDict, self).__setitem__(key, value) -######################################################################## -# 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): logging.debug("%s %s %s %s", cls, name, bases, attrs) diff --git a/src/nbblib/vcs.py b/src/nbblib/vcs.py index c842fe1..c7ac22e 100644 --- a/src/nbblib/vcs.py +++ b/src/nbblib/vcs.py @@ -3,10 +3,9 @@ import logging import urlparse from nbblib.package import * -from nbblib.plugins import * from nbblib.progutils import * -from nbblib import newplugins +from nbblib import newplugins as plugins class AbstractConfig(object): @@ -34,66 +33,79 @@ class AbstractConfig(object): # VCS Source Tree plugin system ######################################################################## -class NotAVCSourceTree(Exception): - pass +class NotAVCSourceTree(plugins.PluginNoMatch): + def __init__(self, srcdir): + super(NotAVCSourceTree, self).__init__() + self.srcdir = srcdir + def __str__(self): + return "Unknown VCS source tree type: %s" % repr(self.srcdir) -class AmbigousVCSource(Exception): - def __init__(self, srcdir, matches): - super(AmbigousVCSource, self).__init__() +class AmbigousVCSDetection(plugins.AmbigousPluginDetection): + def __init__(self, matches, cls, context, srcdir): + super(AmbigousVCSDetection, self).__init__(matches, cls, context) 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')] + - [fmt % (m.name, m.branch_name(), m.tree_root()) for m in self.matches]) - return ("More than one source tree VCS type detected for '%s':\n#%s" - % (self.srcdir, '\n '.join(alist))) + # We possibly need to re-add m.tree_root here again soon + alist = [('VCS type', 'Branch name', )] + alist.extend(((name, m.branch_name, ) + for name, m in self.matches.iteritems())) + table = "\n".join([" %-9s %s" % a for a in alist]) + return "Ambigous VCS types detected for %s:\n%s" % (repr(self.srcdir), table) -class VCSourceTree(newplugins.GenericDetectPlugin): +class VCSourceTree(plugins.GenericDetectPlugin): """ Mount point for plugins which refer to actions that can be performed. - Plugins implementing this reference should provide the following + Plugins implementing this reference must 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 + __init__ + Must "raise self.no_match_exception()" if it does not match. + and the other abstract methods below """ - __metaclass__ = GenericPluginMeta - no_match_exception = PluginNoMatch - ambigous_match_exception = AmbigousPluginDetection + __metaclass__ = plugins.GenericPluginMeta + no_match_exception = NotAVCSourceTree + ambigous_match_exception = AmbigousVCSDetection @classmethod - def validate(cls, obj, *args, **kwargs): - srcdir = args[0] - return obj.tree_root() == srcdir + def validate(cls, obj, srcdir): + logging.debug("cls %s", cls) + logging.debug("obj %s", obj) + logging.debug("srcdir %s", srcdir) + return obj.tree_root == srcdir def get_config(self): """Get configuration object which determines builddir etc""" - return AbstractConfig(self.tree_root(), self.branch_name()) + return AbstractConfig(self.tree_root, self.branch_name) config = property(get_config) - def tree_root(self): + def _get_tree_root(self): """Get absolute path to source tree root""" raise NotImplementedError() - def branch_name(self): + def get_tree_root(self): + return self._get_tree_root() + tree_root = property(get_tree_root) + + def _get_branch_name(self): """Return name identifying the branch""" raise NotImplementedError() + def get_branch_name(self): + """Return name identifying the branch""" + return self._get_branch_name() + branch_name = property(get_branch_name) def __str__(self): return repr(self) def __repr__(self): return "<%s(%s, %s)>" % (self.__class__.__name__, - repr(self.tree_root()), - repr(self.branch_name())) + repr(self.tree_root), + repr(self.branch_name)) ######################################################################## @@ -109,20 +121,20 @@ class GitSourceTree(VCSourceTree): os.chdir(srcdir) if "true" != prog_stdout(["git", "rev-parse", "--is-inside-work-tree"]): - raise NotAVCSourceTree() + raise self.no_match_exception(srcdir) 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()) + return GitConfig(self.tree_root, self.branch_name) config = property(get_config) - def tree_root(self): + def _get_tree_root(self): return self.__tree_root - def branch_name(self): + def _get_branch_name(self): bname = prog_stdout(["git", "symbolic-ref", "HEAD"]) refs,heads,branch = bname.split('/') assert(refs=='refs' and heads=='heads') @@ -178,9 +190,9 @@ class BzrSourceTree(VCSourceTree): import bzrlib.workingtree wt,b = bzrlib.workingtree.WorkingTree.open_containing(srcdir) except bzrlib.errors.NotBranchError: - raise NotAVCSourceTree() + raise self.no_match_exception(srcdir) except ImportError: - raise NotAVCSourceTree() + raise self.no_match_exception(srcdir) self.wt = wt #print "wt:", wt #print "wt:", dir(wt) @@ -191,13 +203,13 @@ class BzrSourceTree(VCSourceTree): #print "wt.branch.base:", wt.branch.base #print "wt.branch.basis_tree:", wt.branch.basis_tree() - def tree_root(self): + def _get_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): + def _get_branch_name(self): return self.wt.branch.nick -- cgit