jhbuild r2304 - in trunk: . buildbot jhbuild/buildbot jhbuild/commands



Author: fpeters
Date: Sun Aug 24 11:44:26 2008
New Revision: 2304
URL: http://svn.gnome.org/viewvc/jhbuild?rev=2304&view=rev

Log:
* buildbot/master.cfg, jhbuild/buildbot/__init__.py,
jhbuild/commands/bot.py: moved build master "unconfiguration" stuff
out of master.cfg.



Modified:
   trunk/ChangeLog
   trunk/buildbot/master.cfg
   trunk/jhbuild/buildbot/__init__.py
   trunk/jhbuild/commands/bot.py

Modified: trunk/buildbot/master.cfg
==============================================================================
--- trunk/buildbot/master.cfg	(original)
+++ trunk/buildbot/master.cfg	Sun Aug 24 11:44:26 2008
@@ -10,112 +10,16 @@
 # buildmaster. They are documented in docs/config.xhtml .
 
 import buildbot
-import sys
-import os
 
 # This is the dictionary that the buildmaster pays attention to. We also use
 # a shorter alias to save typing.
 c = BuildmasterConfig = {}
 
-####### BUILDSLAVES
-
-# the 'slaves' list is read from the 'slaves.csv' file in the current directory
-# it is a CSV file structured like this:
-#  slavename,password
-from buildbot.buildslave import BuildSlave
-import csv
-
-if not os.path.exists('slaves.csv'):
-    c['slaves'] = []
-else:
-    c['slaves'] = [BuildSlave(x[0], x[1])
-                   for x in csv.reader(file('slaves.csv'))
-                   if x and not x[0].startswith('#')]
-
-if len(c['slaves']) == 0:
-    print >> sys.stderr, 'You must fill slaves.csv with slaves'
-
-# to limit to two concurrent builds on a slave, use
-#  c['slaves'] = [BuildSlave("bot1name", "bot1passwd", max_builds=2)]
-
-
 # 'slavePortnum' defines the TCP port to listen on. This must match the value
 # configured into the buildslaves (with their --master option)
 
 c['slavePortnum'] = 9070
 
-####### PROJECTS
-
-# the 'projects' list defines which jhbuild modules that to build
-from jhbuild.buildbot import jhbuild_list
-c['projects'] = jhbuild_list()
-
-####### CHANGESOURCES
-
-# the 'change_source' setting tells the buildmaster how it should find out
-# about source code changes. Any class which implements IChangeSource can be
-# put here: there are several in buildbot/changes/*.py to choose from.
-
-from jhbuild.buildbot.changes import GnomeMaildirSource
-if jhbuild_config.jhbuildbot_svn_commits_box:
-    # trigger builds from mails to svn-commit-list
-    # (note Maildir must be correct, or everything will fail)
-    c['change_source'] = GnomeMaildirSource(
-        jhbuild_config.jhbuildbot_svn_commits_box, prefix=None)
-else:
-    # support injection (use 'buildbot sendchange')
-    from buildbot.changes.pb import PBChangeSource
-    c['change_source'] = PBChangeSource()
-
-####### SCHEDULERS
-
-## configure the Schedulers
-from jhbuild.buildbot.scheduler import SerialScheduler, OnCommitScheduler
-c['schedulers'] = []
-for slave in c['slaves']:
-    s = None
-    for project in c['projects']:
-        buildername = str('%s-%s' % (project, slave.slavename))
-        s = SerialScheduler(buildername, project, upstream=s,
-                            builderNames=[buildername])
-        c['schedulers'].append(s)
-        if jhbuild_config.jhbuildbot_svn_commits_box:
-            s2 = OnCommitScheduler('oc-' + buildername, project, builderNames=[buildername])
-            c['schedulers'].append(s2)
-
-####### BUILDERS
-
-# the 'builders' list defines the Builders. Each one is configured with a
-# dictionary, using the following keys:
-#  name (required): the name used to describe this bilder
-#  slavename (required): which slave to use, must appear in c['bots']
-#  builddir (required): which subdirectory to run the builder in
-#  factory (required): a BuildFactory to define how the build is run
-#  periodicBuildTime (optional): if set, force a build every N seconds
-
-# buildbot/process/factory.py provides several BuildFactory classes you can
-# start with, which implement build processes for common targets (GNU
-# autoconf projects, CPAN perl modules, etc). The factory.BuildFactory is the
-# base class, and is configured with a series of BuildSteps. When the build
-# is run, the appropriate buildslave is told to execute each Step in turn.
-
-# the first BuildStep is typically responsible for obtaining a copy of the
-# sources. There are source-obtaining Steps in buildbot/steps/source.py for
-# CVS, SVN, and others.
-
-from jhbuild.buildbot.factory import JHBuildFactory
-c['builders'] = []
-for project in c['projects']:
-    for slave in c['slaves']:
-        f = JHBuildFactory(project)
-        c['builders'].append({
-            'name' : "%s-%s" % (project, slave.slavename),
-            'slavename' : slave.slavename,
-            'builddir' : 'builddir/%s.%s' % (project, slave.slavename),
-            'factory' : f,
-            'category' : project
-        })
-
 ####### STATUS TARGETS
 
 # 'status' is a list of Status Targets. The results of each build will be
@@ -124,15 +28,6 @@
 
 c['status'] = []
 
-from jhbuild.buildbot.status.web import JHBuildWebStatus
-c['status'].append(
-    JHBuildWebStatus(
-        jhbuild_config.moduleset,
-        c['projects'],
-        [x.slavename for x in c['slaves']],
-        http_port=8080, allowForce=True)
-)
-
 # from buildbot.status import mail
 # c['status'].append(mail.MailNotifier(fromaddr="buildbot localhost",
 #                                      extraRecipients=["builds example com"],

Modified: trunk/jhbuild/buildbot/__init__.py
==============================================================================
--- trunk/jhbuild/buildbot/__init__.py	(original)
+++ trunk/jhbuild/buildbot/__init__.py	Sun Aug 24 11:44:26 2008
@@ -1,28 +0,0 @@
-# jhbuild - a build script for GNOME 1.x and 2.x
-# Copyright (C) 2008  apinheiro igalia com, John Carr, Frederic Peters
-#
-#   __init__.py: utility functions for master.cfg
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import jhbuild.moduleset
-
-def jhbuild_list():
-    module_set = jhbuild.moduleset.load(jhbuild_config)
-    module_list = module_set.get_module_list(
-            jhbuild_config.modules,
-            jhbuild_config.skip,
-            include_optional_modules=True)
-    return [x.name for x in module_list if not x.name.startswith('meta-')]

Modified: trunk/jhbuild/commands/bot.py
==============================================================================
--- trunk/jhbuild/commands/bot.py	(original)
+++ trunk/jhbuild/commands/bot.py	Sun Aug 24 11:44:26 2008
@@ -2,7 +2,7 @@
 # Copyright (C) 2001-2006  James Henstridge
 # Copyright (C) 2008 Frederic Peters
 #
-#   bot.py: buildbot slave commands
+#   bot.py: buildbot control commands
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,6 +17,11 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+#
+# Some methods are derived from Buildbot own methods (when it was not possible
+# to override just some parts of them).  Buildbot is also licensed under the
+# GNU General Public License.
 
 import os
 import sys
@@ -24,6 +29,7 @@
 from optparse import make_option
 import socket
 import __builtin__
+import csv
 
 import jhbuild.moduleset
 import jhbuild.frontends
@@ -177,9 +183,382 @@
             def createOrGetApplication(self):
                 return self.application
 
-        from twisted.application import service
+        from twisted.application import service, strports
         from buildbot.master import BuildMaster
         application = service.Application('buildmaster')
+        from buildbot.buildslave import BuildSlave
+
+        from twisted.python import log
+        from twisted.internet import defer
+        from buildbot import interfaces
+        from buildbot.process.properties import Properties
+
+
+        class JhBuildMaster(BuildMaster):
+            jhbuild_config = config
+            def loadConfig(self, f):
+                # modified from parent method to get slaves, projects, change
+                # sources, schedulers, builders and web status ouf of
+                # master.cfg [it would have been cleaner if jhbuild didn't
+                # have to copy all that code.]
+                localDict = {'basedir': os.path.expanduser(self.basedir)}
+                try:
+                    exec f in localDict
+                except:
+                    log.msg("error while parsing config file")
+                    raise
+
+                try:
+                    config = localDict['BuildmasterConfig']
+                except KeyError:
+                    log.err("missing config dictionary")
+                    log.err("config file must define BuildmasterConfig")
+                    raise
+
+                known_keys = ("bots", "slaves",
+                              "sources", "change_source",
+                              "schedulers", "builders",
+                              "slavePortnum", "debugPassword", "manhole",
+                              "status", "projectName", "projectURL", "buildbotURL",
+                              "properties"
+                              )
+                for k in config.keys():
+                    if k not in known_keys:
+                        log.msg("unknown key '%s' defined in config dictionary" % k)
+
+                # the 'slaves' list is read from the 'slaves.csv' file in the
+                # current directory it is a CSV file structured like this:
+                #   slavename,password
+                # it is also possible to define build slave options, for
+                # example:
+                #   slavename,password,max_builds=2
+                # (recognized build slave options are max_build and
+                # missing_timeout)
+                config['slaves'] = []
+                if os.path.exists('slaves.csv'):
+                    for x in csv.reader(file('slaves.csv')):
+                        if not x or x[0].startswith('#'):
+                            continue
+                        kw = {}
+                        for option in x[2:]:
+                            if not '=' in option:
+                                continue
+                            k, v = option.split('=', 1)
+                            if k in ('max_builds', 'missing_timeout'):
+                                v = int(v)
+                            else:
+                                # unrecognized option
+                                continue
+                            kw[k] = v
+                        config['slaves'].append(BuildSlave(x[0], x[1], **kw))
+
+                if len(config['slaves']) == 0:
+                    log.msg('you must fill slaves.csv with slaves')
+
+                module_set = jhbuild.moduleset.load(self.jhbuild_config)
+                module_list = module_set.get_module_list(
+                        self.jhbuild_config.modules,
+                        self.jhbuild_config.skip,
+                        include_optional_modules=True)
+                config['projects'] = [x.name for x in module_list \
+                                      if not x.name.startswith('meta-')]
+
+                if self.jhbuild_config.jhbuildbot_svn_commits_box:
+                    # trigger builds from mails to svn-commit-list
+                    # (note Maildir must be correct, or everything will fail)
+                    from jhbuild.buildbot.changes import GnomeMaildirSource
+                    config['change_source'] = GnomeMaildirSource(
+                            self.jhbuild_config.jhbuildbot_svn_commits_box,
+                            prefix=None)
+                else:
+                    # support injection (use 'buildbot sendchange')
+                    from buildbot.changes.pb import PBChangeSource
+                    c['change_source'] = PBChangeSource()
+
+                # Schedulers
+                from jhbuild.buildbot.scheduler import SerialScheduler, OnCommitScheduler
+                config['schedulers'] = []
+                for slave in config['slaves']:
+                    s = None
+                    for project in config['projects']:
+                        buildername = str('%s-%s' % (project, slave.slavename))
+                        s = SerialScheduler(buildername, project, upstream=s,
+                                            builderNames=[buildername])
+                        config['schedulers'].append(s)
+                        if self.jhbuild_config.jhbuildbot_svn_commits_box:
+                            # schedulers that will launch job when receiving
+                            # change notifications
+                            s2 = OnCommitScheduler('oc-' + buildername,
+                                    project, builderNames=[buildername])
+                            config['schedulers'].append(s2)
+
+                # Builders
+                from jhbuild.buildbot.factory import JHBuildFactory
+                config['builders'] = []
+                for project in config['projects']:
+                    for slave in config['slaves']:
+                        f = JHBuildFactory(project)
+                        config['builders'].append({
+                            'name' : "%s-%s" % (project, slave.slavename),
+                            'slavename' : slave.slavename,
+                            'builddir' : 'builddir/%s.%s' % (project, slave.slavename),
+                            'factory' : f,
+                            'category' : project
+                        })
+
+                # Status targets
+                if not config.has_key('status'):
+                    # let it be possible to define additional status in
+                    # master.cfg
+                    config['status'] = []
+
+                from jhbuild.buildbot.status.web import JHBuildWebStatus
+                config['status'].append(
+                    JHBuildWebStatus(
+                        self.jhbuild_config.moduleset,
+                        config['projects'],
+                        [x.slavename for x in config['slaves']],
+                        http_port=8080, allowForce=True)
+                )
+
+                # remaining of the method is a straight copy from buildbot
+                # ...
+                try:
+                    # required
+                    schedulers = config['schedulers']
+                    builders = config['builders']
+                    for k in builders:
+                        if k['name'].startswith("_"):
+                            errmsg = ("builder names must not start with an "
+                                      "underscore: " + k['name'])
+                            log.err(errmsg)
+                            raise ValueError(errmsg)
+
+                    slavePortnum = config['slavePortnum']
+                    #slaves = config['slaves']
+                    #change_source = config['change_source']
+
+                    # optional
+                    debugPassword = config.get('debugPassword')
+                    manhole = config.get('manhole')
+                    status = config.get('status', [])
+                    projectName = config.get('projectName')
+                    projectURL = config.get('projectURL')
+                    buildbotURL = config.get('buildbotURL')
+                    properties = config.get('properties', {})
+
+                except KeyError, e:
+                    log.msg("config dictionary is missing a required parameter")
+                    log.msg("leaving old configuration in place")
+                    raise
+
+                #if "bots" in config:
+                #    raise KeyError("c['bots'] is no longer accepted")
+
+                slaves = config.get('slaves', [])
+                if "bots" in config:
+                    m = ("c['bots'] is deprecated as of 0.7.6 and will be "
+                         "removed by 0.8.0 . Please use c['slaves'] instead.")
+                    log.msg(m)
+                    warnings.warn(m, DeprecationWarning)
+                    for name, passwd in config['bots']:
+                        slaves.append(BuildSlave(name, passwd))
+
+                if "bots" not in config and "slaves" not in config:
+                    log.msg("config dictionary must have either 'bots' or 'slaves'")
+                    log.msg("leaving old configuration in place")
+                    raise KeyError("must have either 'bots' or 'slaves'")
+
+                #if "sources" in config:
+                #    raise KeyError("c['sources'] is no longer accepted")
+
+                change_source = config.get('change_source', [])
+                if isinstance(change_source, (list, tuple)):
+                    change_sources = change_source
+                else:
+                    change_sources = [change_source]
+                if "sources" in config:
+                    m = ("c['sources'] is deprecated as of 0.7.6 and will be "
+                         "removed by 0.8.0 . Please use c['change_source'] instead.")
+                    log.msg(m)
+                    warnings.warn(m, DeprecationWarning)
+                    for s in config['sources']:
+                        change_sources.append(s)
+
+                # do some validation first
+                for s in slaves:
+                    assert isinstance(s, BuildSlave)
+                    if s.slavename in ("debug", "change", "status"):
+                        raise KeyError, "reserved name '%s' used for a bot" % s.slavename
+                if config.has_key('interlocks'):
+                    raise KeyError("c['interlocks'] is no longer accepted")
+
+                assert isinstance(change_sources, (list, tuple))
+                for s in change_sources:
+                    assert interfaces.IChangeSource(s, None)
+                # this assertion catches c['schedulers'] = Scheduler(), since
+                # Schedulers are service.MultiServices and thus iterable.
+                errmsg = "c['schedulers'] must be a list of Scheduler instances"
+                assert isinstance(schedulers, (list, tuple)), errmsg
+                for s in schedulers:
+                    assert interfaces.IScheduler(s, None), errmsg
+                assert isinstance(status, (list, tuple))
+                for s in status:
+                    assert interfaces.IStatusReceiver(s, None)
+
+                slavenames = [s.slavename for s in slaves]
+                buildernames = []
+                dirnames = []
+                for b in builders:
+                    if type(b) is tuple:
+                        raise ValueError("builder %s must be defined with a dict, "
+                                         "not a tuple" % b[0])
+                    if b.has_key('slavename') and b['slavename'] not in slavenames:
+                        raise ValueError("builder %s uses undefined slave %s" \
+                                         % (b['name'], b['slavename']))
+                    for n in b.get('slavenames', []):
+                        if n not in slavenames:
+                            raise ValueError("builder %s uses undefined slave %s" \
+                                             % (b['name'], n))
+                    if b['name'] in buildernames:
+                        raise ValueError("duplicate builder name %s"
+                                         % b['name'])
+                    buildernames.append(b['name'])
+                    if b['builddir'] in dirnames:
+                        raise ValueError("builder %s reuses builddir %s"
+                                         % (b['name'], b['builddir']))
+                    dirnames.append(b['builddir'])
+
+                unscheduled_buildernames = buildernames[:]
+                schedulernames = []
+                for s in schedulers:
+                    for b in s.listBuilderNames():
+                        assert b in buildernames, \
+                               "%s uses unknown builder %s" % (s, b)
+                        if b in unscheduled_buildernames:
+                            unscheduled_buildernames.remove(b)
+
+                    if s.name in schedulernames:
+                        # TODO: schedulers share a namespace with other Service
+                        # children of the BuildMaster node, like status plugins, the
+                        # Manhole, the ChangeMaster, and the BotMaster (although most
+                        # of these don't have names)
+                        msg = ("Schedulers must have unique names, but "
+                               "'%s' was a duplicate" % (s.name,))
+                        raise ValueError(msg)
+                    schedulernames.append(s.name)
+
+                if unscheduled_buildernames:
+                    log.msg("Warning: some Builders have no Schedulers to drive them:"
+                            " %s" % (unscheduled_buildernames,))
+
+                # assert that all locks used by the Builds and their Steps are
+                # uniquely named.
+                locks = {}
+                for b in builders:
+                    for l in b.get('locks', []):
+                        if locks.has_key(l.name):
+                            if locks[l.name] is not l:
+                                raise ValueError("Two different locks (%s and %s) "
+                                                 "share the name %s"
+                                                 % (l, locks[l.name], l.name))
+                        else:
+                            locks[l.name] = l
+                    # TODO: this will break with any BuildFactory that doesn't use a
+                    # .steps list, but I think the verification step is more
+                    # important.
+                    for s in b['factory'].steps:
+                        for l in s[1].get('locks', []):
+                            if locks.has_key(l.name):
+                                if locks[l.name] is not l:
+                                    raise ValueError("Two different locks (%s and %s)"
+                                                     " share the name %s"
+                                                     % (l, locks[l.name], l.name))
+                            else:
+                                locks[l.name] = l
+
+                if not isinstance(properties, dict):
+                    raise ValueError("c['properties'] must be a dictionary")
+
+                # slavePortnum supposed to be a strports specification
+                if type(slavePortnum) is int:
+                    slavePortnum = "tcp:%d" % slavePortnum
+
+                # now we're committed to implementing the new configuration, so do
+                # it atomically
+                # TODO: actually, this is spread across a couple of Deferreds, so it
+                # really isn't atomic.
+
+                d = defer.succeed(None)
+
+                self.projectName = projectName
+                self.projectURL = projectURL
+                self.buildbotURL = buildbotURL
+                
+                self.properties = Properties()
+                self.properties.update(properties, self.configFileName)
+
+                # self.slaves: Disconnect any that were attached and removed from the
+                # list. Update self.checker with the new list of passwords, including
+                # debug/change/status.
+                d.addCallback(lambda res: self.loadConfig_Slaves(slaves))
+
+                # self.debugPassword
+                if debugPassword:
+                    self.checker.addUser("debug", debugPassword)
+                    self.debugPassword = debugPassword
+
+                # self.manhole
+                if manhole != self.manhole:
+                    # changing
+                    if self.manhole:
+                        # disownServiceParent may return a Deferred
+                        d.addCallback(lambda res: self.manhole.disownServiceParent())
+                        def _remove(res):
+                            self.manhole = None
+                            return res
+                        d.addCallback(_remove)
+                    if manhole:
+                        def _add(res):
+                            self.manhole = manhole
+                            manhole.setServiceParent(self)
+                        d.addCallback(_add)
+
+                # add/remove self.botmaster.builders to match builders. The
+                # botmaster will handle startup/shutdown issues.
+                d.addCallback(lambda res: self.loadConfig_Builders(builders))
+
+                d.addCallback(lambda res: self.loadConfig_status(status))
+
+                # Schedulers are added after Builders in case they start right away
+                d.addCallback(lambda res: self.loadConfig_Schedulers(schedulers))
+                # and Sources go after Schedulers for the same reason
+                d.addCallback(lambda res: self.loadConfig_Sources(change_sources))
+
+                # self.slavePort
+                if self.slavePortnum != slavePortnum:
+                    if self.slavePort:
+                        def closeSlavePort(res):
+                            d1 = self.slavePort.disownServiceParent()
+                            self.slavePort = None
+                            return d1
+                        d.addCallback(closeSlavePort)
+                    if slavePortnum is not None:
+                        def openSlavePort(res):
+                            self.slavePort = strports.service(slavePortnum,
+                                                              self.slaveFactory)
+                            self.slavePort.setServiceParent(self)
+                        d.addCallback(openSlavePort)
+                        log.msg("BuildMaster listening on port %s" % slavePortnum)
+                    self.slavePortnum = slavePortnum
+
+                log.msg("configuration update started")
+                def _done(res):
+                    self.readConfig = True
+                    log.msg("configuration update complete")
+                d.addCallback(_done)
+                d.addCallback(lambda res: self.botmaster.maybeStartAllBuilds())
+                return d
 
         basedir = os.path.join(
                 os.path.dirname(os.path.abspath(__file__)),
@@ -189,7 +568,7 @@
             os.makedirs(os.path.join(basedir, 'builddir'))
         master_cfg_path = os.path.join(basedir, 'master.cfg')
 
-        BuildMaster(basedir, master_cfg_path).setServiceParent(application)
+        JhBuildMaster(basedir, master_cfg_path).setServiceParent(application)
 
         JhBuildbotApplicationRunner.application = application
         JhBuildbotApplicationRunner(options).run()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]