1 # jhbuild - a build script for GNOME 1.x and 2.x
2 # Copyright (C) 2001-2006 James Henstridge
3 # Copyright (C) 2008 Frederic Peters
5 # bot.py: buildbot control commands
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # Some methods are derived from Buildbot own methods (when it was not possible
23 # to override just some parts of them). Buildbot is also licensed under the
24 # GNU General Public License.
30 from optparse
import make_option
37 import elementtree
.ElementTree
as ET
39 import xml
.etree
.ElementTree
as ET
41 import jhbuild
.moduleset
42 import jhbuild
.frontends
43 from jhbuild
.commands
import Command
, register_command
44 from jhbuild
.commands
.base
import cmd_build
45 from jhbuild
.config
import addpath
46 from jhbuild
.errors
import UsageError
, FatalError
, CommandError
53 class cmd_bot(Command
):
54 doc
= N_('Control buildbot')
57 usage_args
= N_('[ options ... ]')
60 Command
.__init
__(self
, [
61 make_option('--setup',
62 action
='store_true', dest
='setup', default
=False,
63 help=_('setup a buildbot environment')),
64 make_option('--start',
65 action
='store_true', dest
='start', default
=False,
66 help=_('start a buildbot slave server')),
68 action
='store_true', dest
='stop', default
=False,
69 help=_('stop a buildbot slave server')),
70 make_option('--start-server',
71 action
='store_true', dest
='start_server', default
=False,
72 help=_('start a buildbot master server')),
73 make_option('--reload-server-config',
74 action
='store_true', dest
='reload_server_config', default
=False,
75 help=_('reload a buildbot master server configuration')),
76 make_option('--stop-server',
77 action
='store_true', dest
='stop_server', default
=False,
78 help=_('stop a buildbot master server')),
79 make_option('--daemon',
80 action
='store_true', dest
='daemon', default
=False,
81 help=_('start as daemon')),
82 make_option('--pidfile', metavar
='PIDFILE',
83 action
='store', dest
='pidfile', default
=None,
84 help=_('pid file location')),
85 make_option('--logfile', metavar
='LOGFILE',
86 action
='store', dest
='logfile', default
=None,
87 help=_('log file location')),
88 make_option('--slaves-dir', metavar
='SLAVESDIR',
89 action
='store', dest
='slaves_dir', default
=None,
90 help=_('directory with slave files (only with --start-server)')),
91 make_option('--buildbot-dir', metavar
='BUILDBOTDIR',
92 action
='store', dest
='buildbot_dir', default
=None,
93 help=_('directory with buildbot work files (only with --start-server)')),
94 make_option('--mastercfg', metavar
='CFGFILE',
95 action
='store', dest
='mastercfgfile', default
=None,
96 help=_('master cfg file location (only with --start-server)')),
98 action
='store_true', dest
='step', default
=False,
99 help=_('exec a buildbot step (internal use only)')),
102 def run(self
, config
, options
, args
, help=None):
104 return self
.setup(config
)
109 pythonversion
= 'python' + str(sys
.version_info
[0]) + '.' + str(sys
.version_info
[1])
110 pythonpath
= os
.path
.join(config
.prefix
, 'lib', pythonversion
, 'site-packages')
111 site
.addsitedir(pythonpath
)
113 pythonpath
= os
.path
.join(config
.prefix
, 'lib64', pythonversion
, 'site-packages')
114 site
.addsitedir(pythonpath
)
118 raise FatalError(_('buildbot and twisted not found, run jhbuild bot --setup'))
120 # make jhbuild config file accessible to buildbot files
121 # (master.cfg , steps.py, etc.)
122 __builtin__
.__dict
__['jhbuild_config'] = config
127 slaves_dir
= config
.jhbuildbot_slaves_dir
128 mastercfgfile
= config
.jhbuildbot_mastercfg
129 buildbot_dir
= config
.jhbuildbot_dir
134 pidfile
= options
.pidfile
136 logfile
= options
.logfile
137 if options
.slaves_dir
:
138 slaves_dir
= options
.slaves_dir
139 if options
.mastercfgfile
:
140 mastercfgfile
= options
.mastercfgfile
141 if options
.buildbot_dir
:
142 buildbot_dir
= os
.path
.abspath(options
.buildbot_dir
)
145 return self
.start(config
, daemonize
, pidfile
, logfile
)
148 os
.environ
['JHBUILDRC'] = config
.filename
149 os
.environ
['LC_ALL'] = 'C'
150 os
.environ
['LANGUAGE'] = 'C'
151 os
.environ
['LANG'] = 'C'
152 __builtin__
.__dict
__['_'] = lambda x
: x
153 config
.interact
= False
154 config
.nonetwork
= True
155 os
.environ
['TERM'] = 'dumb'
156 if args
[0] in ('update', 'build', 'check', 'clean'):
157 module_set
= jhbuild
.moduleset
.load(config
)
158 buildscript
= jhbuild
.frontends
.get_buildscript(config
,
159 [module_set
.get_module(x
, ignore_case
=True) for x
in args
[1:]])
161 if args
[0] == 'update':
162 config
.nonetwork
= False
163 phases
= ['checkout']
164 elif args
[0] == 'build':
165 config
.alwaysautogen
= True
166 # make check will be run in another step
167 config
.makecheck
= False
168 config
.build_targets
= ['install']
169 elif args
[0] == 'check':
170 config
.makecheck
= True
171 config
.build_targets
= ['check']
173 elif args
[0] == 'clean':
175 rc
= buildscript
.build(phases
=phases
)
178 rc
= jhbuild
.commands
.run(command
, config
, args
[1:], help=None)
181 if options
.start_server
:
182 return self
.start_server(config
, daemonize
, pidfile
, logfile
,
183 slaves_dir
, mastercfgfile
, buildbot_dir
)
185 if options
.stop
or options
.stop_server
:
186 return self
.stop(config
, pidfile
)
188 if options
.reload_server_config
:
189 return self
.reload_server_config(config
, pidfile
)
191 def setup(self
, config
):
192 module_set
= jhbuild
.moduleset
.load(config
, 'buildbot')
193 module_list
= module_set
.get_module_list('all', config
.skip
)
194 build
= jhbuild
.frontends
.get_buildscript(config
, module_list
)
197 def start(self
, config
, daemonize
, pidfile
, logfile
):
198 from twisted
.application
import service
199 application
= service
.Application('buildslave')
200 if ':' in config
.jhbuildbot_master
:
201 master_host
, master_port
= config
.jhbuildbot_master
.split(':')
202 master_port
= int(master_port
)
204 master_host
, master_port
= config
.jhbuildbot_master
, 9070
206 slave_name
= config
.jhbuildbot_slavename
or socket
.gethostname()
211 basedir
= os
.path
.join(config
.checkoutroot
, 'jhbuildbot')
212 if not os
.path
.exists(os
.path
.join(basedir
, 'builddir')):
213 os
.makedirs(os
.path
.join(basedir
, 'builddir'))
216 from buildbot
.slave
.bot
import BuildSlave
217 s
= BuildSlave(master_host
, master_port
,
218 slave_name
, config
.jhbuildbot_password
, basedir
,
219 keepalive
, usepty
, umask
=umask
)
220 s
.setServiceParent(application
)
223 from twisted
.scripts
._twistd
_unix
import UnixApplicationRunner
, ServerOptions
227 opts
.append('--nodaemon')
229 opts
.extend(['--pidfile', pidfile
])
231 opts
.extend(['--logfile', logfile
])
232 options
= ServerOptions()
233 options
.parseOptions(opts
)
235 class JhBuildbotApplicationRunner(UnixApplicationRunner
):
238 def createOrGetApplication(self
):
239 return self
.application
241 JhBuildbotApplicationRunner
.application
= application
242 JhBuildbotApplicationRunner(options
).run()
244 def start_server(self
, config
, daemonize
, pidfile
, logfile
, slaves_dir
,
245 mastercfgfile
, buildbot_dir
):
247 from twisted
.scripts
._twistd
_unix
import UnixApplicationRunner
, ServerOptions
251 opts
.append('--nodaemon')
253 opts
.extend(['--pidfile', pidfile
])
255 opts
.extend(['--logfile', logfile
])
256 options
= ServerOptions()
257 options
.parseOptions(opts
)
259 class JhBuildbotApplicationRunner(UnixApplicationRunner
):
262 def createOrGetApplication(self
):
263 return self
.application
265 from twisted
.application
import service
, strports
266 from buildbot
.master
import BuildMaster
267 application
= service
.Application('buildmaster')
268 from buildbot
.buildslave
import BuildSlave
270 from twisted
.python
import log
271 from twisted
.internet
import defer
272 from buildbot
import interfaces
273 from buildbot
.process
.properties
import Properties
275 class JhBuildSlave(BuildSlave
):
287 run_coverage_report
= False
288 run_clean_afterwards
= False
290 def load_extra_configuration(self
, slaves_dir
):
291 from twisted
.python
import log
292 slave_xml_file
= os
.path
.join(slaves_dir
, self
.slavename
+ '.xml')
293 if not os
.path
.exists(slave_xml_file
):
294 log
.msg(_('No description for slave %s.') % self
.slavename
)
297 cfg
= ET
.parse(slave_xml_file
)
298 except: # parse error
299 log
.msg(_('Failed to parse slave config for %s.') % self
.slavename
)
302 for attribute
in ('config/max_builds', 'config/missing_timeout',
303 'config/run_checks', 'config/run_coverage_report',
304 'config/run_clean_afterwards',
306 'nightly_scheduler/minute',
307 'nightly_scheduler/hour',
308 'nightly_scheduler/dayOfMonth',
309 'nightly_scheduler/month',
310 'nightly_scheduler/dayOfWeek',
311 'info/contact_name', 'info/contact_email',
312 'info/url', 'info/distribution', 'info/architecture',
314 attr_name
= attribute
.split('/')[-1]
316 value
= cfg
.find(attribute
).text
317 except AttributeError:
320 if attr_name
in ('max_builds', 'missing_timeout'): # int value
326 if attr_name
in ('run_checks', 'run_coverage_report', 'run_clean_afterwards'):
327 value
= (value
== 'yes')
329 if attr_name
in ('minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek'):
335 setattr(self
, attr_name
, value
)
337 if self
.scheduler
== 'nightly':
338 self
.nightly_kwargs
= {}
339 for attr_name
in ('minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek'):
340 if hasattr(self
, attr_name
):
341 self
.nightly_kwargs
[attr_name
] = getattr(self
, attr_name
)
343 class JhBuildMaster(BuildMaster
):
344 jhbuild_config
= config
345 def loadConfig(self
, f
):
346 # modified from parent method to get slaves, projects, change
347 # sources, schedulers, builders and web status ouf of
348 # master.cfg [it would have been cleaner if jhbuild didn't
349 # have to copy all that code.]
350 localDict
= {'basedir': os
.path
.expanduser(self
.basedir
)}
354 log
.msg("error while parsing config file")
357 jhbuild_config
.load()
360 config
= localDict
['BuildmasterConfig']
362 log
.err("missing config dictionary")
363 log
.err("config file must define BuildmasterConfig")
366 known_keys
= ("bots", "slaves",
367 "sources", "change_source",
368 "schedulers", "builders", "mergeRequests",
369 "slavePortnum", "debugPassword", "logCompressionLimit",
370 "manhole", "status", "projectName", "projectURL",
371 "buildbotURL", "properties", "prioritizeBuilders",
372 "eventHorizon", "buildCacheSize", "logHorizon", "buildHorizon",
373 "changeHorizon", "logMaxSize", "logMaxTailSize",
374 "logCompressionMethod",
376 for k
in config
.keys():
377 if k
not in known_keys
:
378 log
.msg("unknown key '%s' defined in config dictionary" % k
)
380 # the 'slaves' list is read from the 'slaves.csv' file in the
381 # current directory (unless instructed different from command line)
382 # it is a CSV file structured like this:
384 config
['slaves'] = []
385 slaves_csv_file
= os
.path
.join(slaves_dir
, 'slaves.csv')
386 if os
.path
.exists(slaves_csv_file
):
387 for x
in csv
.reader(file(slaves_csv_file
)):
388 if not x
or x
[0].startswith('#'):
391 build_slave
= JhBuildSlave(x
[0], x
[1])
392 build_slave
.load_extra_configuration(slaves_dir
)
393 config
['slaves'].append(build_slave
)
395 if len(config
['slaves']) == 0:
396 log
.msg('you must fill slaves.csv with slaves')
398 module_set
= jhbuild
.moduleset
.load(self
.jhbuild_config
)
399 module_list
= module_set
.get_module_list(
400 self
.jhbuild_config
.modules
,
401 self
.jhbuild_config
.skip
,
402 include_optional_modules
=True)
403 config
['projects'] = [x
.name
for x
in module_list \
404 if not x
.name
.startswith('meta-')]
406 if self
.jhbuild_config
.jhbuildbot_svn_commits_box
:
407 # trigger builds from mails to svn-commit-list
408 # (note Maildir must be correct, or everything will fail)
409 from jhbuild
.buildbot
.changes
import GnomeMaildirSource
410 config
['change_source'] = GnomeMaildirSource(
411 self
.jhbuild_config
.jhbuildbot_svn_commits_box
,
414 # support injection (use 'buildbot sendchange')
415 from buildbot
.changes
.pb
import PBChangeSource
416 config
['change_source'] = PBChangeSource()
419 from jhbuild
.buildbot
.scheduler
import SerialScheduler
, NightlySerialScheduler
, OnCommitScheduler
420 config
['schedulers'] = []
421 for slave
in config
['slaves']:
423 for project
in config
['projects']:
424 buildername
= str('%s-%s' % (project
, slave
.slavename
))
425 scheduler_kwargs
= {}
426 if slave
.scheduler
== 'nightly':
427 scheduler_class
= NightlySerialScheduler
428 scheduler_kwargs
= slave
.nightly_kwargs
430 scheduler_class
= SerialScheduler
431 s
= scheduler_class(buildername
, project
, upstream
=s
,
432 builderNames
=[buildername
],
434 config
['schedulers'].append(s
)
435 if self
.jhbuild_config
.jhbuildbot_svn_commits_box
:
436 # schedulers that will launch job when receiving
437 # change notifications
438 s2
= OnCommitScheduler('oc-' + buildername
,
439 project
, builderNames
=[buildername
])
440 config
['schedulers'].append(s2
)
443 from jhbuild
.buildbot
.factory
import JHBuildFactory
444 config
['builders'] = []
445 for project
in config
['projects']:
446 for slave
in config
['slaves']:
447 f
= JHBuildFactory(project
, slave
)
448 config
['builders'].append({
449 'name' : "%s-%s" % (project
, slave
.slavename
),
450 'slavename' : slave
.slavename
,
451 'builddir' : 'builddir/%s.%s' % (project
, slave
.slavename
),
457 if not config
.has_key('status'):
458 # let it be possible to define additional status in
460 config
['status'] = []
462 from jhbuild
.buildbot
.status
.web
import JHBuildWebStatus
463 config
['status'].append(
465 self
.jhbuild_config
.moduleset
,
467 [x
.slavename
for x
in config
['slaves']],
468 http_port
=8080, allowForce
=True)
471 # remaining of the method is a straight copy from buildbot
475 schedulers
= config
['schedulers']
476 builders
= config
['builders']
477 slavePortnum
= config
['slavePortnum']
478 #slaves = config['slaves']
479 #change_source = config['change_source']
482 debugPassword
= config
.get('debugPassword')
483 manhole
= config
.get('manhole')
484 status
= config
.get('status', [])
485 projectName
= config
.get('projectName')
486 projectURL
= config
.get('projectURL')
487 buildbotURL
= config
.get('buildbotURL')
488 properties
= config
.get('properties', {})
489 buildCacheSize
= config
.get('buildCacheSize', None)
490 eventHorizon
= config
.get('eventHorizon', None)
491 logHorizon
= config
.get('logHorizon', None)
492 buildHorizon
= config
.get('buildHorizon', None)
493 logCompressionLimit
= config
.get('logCompressionLimit', 4*1024)
494 if logCompressionLimit
is not None and not \
495 isinstance(logCompressionLimit
, int):
496 raise ValueError("logCompressionLimit needs to be bool or int")
497 logCompressionMethod
= config
.get('logCompressionMethod', "bz2")
498 if logCompressionMethod
not in ('bz2', 'gz'):
499 raise ValueError("logCompressionMethod needs to be 'bz2', or 'gz'")
500 logMaxSize
= config
.get('logMaxSize')
501 if logMaxSize
is not None and not \
502 isinstance(logMaxSize
, int):
503 raise ValueError("logMaxSize needs to be None or int")
504 logMaxTailSize
= config
.get('logMaxTailSize')
505 if logMaxTailSize
is not None and not \
506 isinstance(logMaxTailSize
, int):
507 raise ValueError("logMaxTailSize needs to be None or int")
508 mergeRequests
= config
.get('mergeRequests')
509 if mergeRequests
is not None and not callable(mergeRequests
):
510 raise ValueError("mergeRequests must be a callable")
511 prioritizeBuilders
= config
.get('prioritizeBuilders')
512 if prioritizeBuilders
is not None and not callable(prioritizeBuilders
):
513 raise ValueError("prioritizeBuilders must be callable")
514 changeHorizon
= config
.get("changeHorizon")
515 if changeHorizon
is not None and not isinstance(changeHorizon
, int):
516 raise ValueError("changeHorizon needs to be an int")
519 log
.msg("config dictionary is missing a required parameter")
520 log
.msg("leaving old configuration in place")
523 #if "bots" in config:
524 # raise KeyError("c['bots'] is no longer accepted")
526 slaves
= config
.get('slaves', [])
528 m
= ("c['bots'] is deprecated as of 0.7.6 and will be "
529 "removed by 0.8.0 . Please use c['slaves'] instead.")
531 warnings
.warn(m
, DeprecationWarning)
532 for name
, passwd
in config
['bots']:
533 slaves
.append(JhBuildSlave(name
, passwd
))
535 if "bots" not in config
and "slaves" not in config
:
536 log
.msg("config dictionary must have either 'bots' or 'slaves'")
537 log
.msg("leaving old configuration in place")
538 raise KeyError("must have either 'bots' or 'slaves'")
540 #if "sources" in config:
541 # raise KeyError("c['sources'] is no longer accepted")
543 if changeHorizon
is not None:
544 self
.change_svc
.changeHorizon
= changeHorizon
546 change_source
= config
.get('change_source', [])
547 if isinstance(change_source
, (list, tuple)):
548 change_sources
= change_source
550 change_sources
= [change_source
]
551 if "sources" in config
:
552 m
= ("c['sources'] is deprecated as of 0.7.6 and will be "
553 "removed by 0.8.0 . Please use c['change_source'] instead.")
555 warnings
.warn(m
, DeprecationWarning)
556 for s
in config
['sources']:
557 change_sources
.append(s
)
559 # do some validation first
561 assert interfaces
.IBuildSlave
.providedBy(s
)
562 if s
.slavename
in ("debug", "change", "status"):
564 "reserved name '%s' used for a bot" % s
.slavename
)
565 if config
.has_key('interlocks'):
566 raise KeyError("c['interlocks'] is no longer accepted")
568 assert isinstance(change_sources
, (list, tuple))
569 for s
in change_sources
:
570 assert interfaces
.IChangeSource(s
, None)
571 # this assertion catches c['schedulers'] = Scheduler(), since
572 # Schedulers are service.MultiServices and thus iterable.
573 errmsg
= "c['schedulers'] must be a list of Scheduler instances"
574 assert isinstance(schedulers
, (list, tuple)), errmsg
576 assert interfaces
.IScheduler(s
, None), errmsg
577 assert isinstance(status
, (list, tuple))
579 assert interfaces
.IStatusReceiver(s
, None)
581 slavenames
= [s
.slavename
for s
in slaves
]
585 # convert builders from objects to config dictionaries
588 if isinstance(b
, buildbot
.config
.BuilderConfig
):
589 builders_dicts
.append(b
.getConfigDict())
590 elif type(b
) is dict:
591 builders_dicts
.append(b
)
593 raise ValueError("builder %s is not a BuilderConfig object (or a dict)" % b
)
594 builders
= builders_dicts
597 if b
.has_key('slavename') and b
['slavename'] not in slavenames
:
598 raise ValueError("builder %s uses undefined slave %s" \
599 % (b
['name'], b
['slavename']))
600 for n
in b
.get('slavenames', []):
601 if n
not in slavenames
:
602 raise ValueError("builder %s uses undefined slave %s" \
604 if b
['name'] in buildernames
:
605 raise ValueError("duplicate builder name %s"
607 buildernames
.append(b
['name'])
609 # sanity check name (BuilderConfig does this too)
610 if b
['name'].startswith("_"):
611 errmsg
= ("builder names must not start with an "
612 "underscore: " + b
['name'])
614 raise ValueError(errmsg
)
616 # Fix the dictionnary with default values, in case this wasn't
617 # specified with a BuilderConfig object (which sets the same defaults)
618 b
.setdefault('builddir', buildbot
.util
.safeTranslate(b
['name']))
619 b
.setdefault('slavebuilddir', b
['builddir'])
621 if b
['builddir'] in dirnames
:
622 raise ValueError("builder %s reuses builddir %s"
623 % (b
['name'], b
['builddir']))
624 dirnames
.append(b
['builddir'])
626 unscheduled_buildernames
= buildernames
[:]
629 for b
in s
.listBuilderNames():
630 assert b
in buildernames
, \
631 "%s uses unknown builder %s" % (s
, b
)
632 if b
in unscheduled_buildernames
:
633 unscheduled_buildernames
.remove(b
)
635 if s
.name
in schedulernames
:
636 # TODO: schedulers share a namespace with other Service
637 # children of the BuildMaster node, like status plugins, the
638 # Manhole, the ChangeMaster, and the BotMaster (although most
639 # of these don't have names)
640 msg
= ("Schedulers must have unique names, but "
641 "'%s' was a duplicate" % (s
.name
,))
642 raise ValueError(msg
)
643 schedulernames
.append(s
.name
)
645 if unscheduled_buildernames
:
646 log
.msg("Warning: some Builders have no Schedulers to drive them:"
647 " %s" % (unscheduled_buildernames
,))
649 # assert that all locks used by the Builds and their Steps are
653 for l
in b
.get('locks', []):
654 if isinstance(l
, locks
.LockAccess
): # User specified access to the lock
656 if lock_dict
.has_key(l
.name
):
657 if lock_dict
[l
.name
] is not l
:
658 raise ValueError("Two different locks (%s and %s) "
660 % (l
, lock_dict
[l
.name
], l
.name
))
662 lock_dict
[l
.name
] = l
663 # TODO: this will break with any BuildFactory that doesn't use a
664 # .steps list, but I think the verification step is more
666 for s
in b
['factory'].steps
:
667 for l
in s
[1].get('locks', []):
668 if isinstance(l
, locks
.LockAccess
): # User specified access to the lock
670 if lock_dict
.has_key(l
.name
):
671 if lock_dict
[l
.name
] is not l
:
672 raise ValueError("Two different locks (%s and %s)"
674 % (l
, lock_dict
[l
.name
], l
.name
))
676 lock_dict
[l
.name
] = l
678 if not isinstance(properties
, dict):
679 raise ValueError("c['properties'] must be a dictionary")
681 # slavePortnum supposed to be a strports specification
682 if type(slavePortnum
) is int:
683 slavePortnum
= "tcp:%d" % slavePortnum
685 # now we're committed to implementing the new configuration, so do
687 # TODO: actually, this is spread across a couple of Deferreds, so it
688 # really isn't atomic.
690 d
= defer
.succeed(None)
692 self
.projectName
= projectName
693 self
.projectURL
= projectURL
694 self
.buildbotURL
= buildbotURL
696 self
.properties
= Properties()
697 self
.properties
.update(properties
, self
.configFileName
)
699 self
.status
.logCompressionLimit
= logCompressionLimit
700 self
.status
.logCompressionMethod
= logCompressionMethod
701 self
.status
.logMaxSize
= logMaxSize
702 self
.status
.logMaxTailSize
= logMaxTailSize
703 # Update any of our existing builders with the current log parameters.
704 # This is required so that the new value is picked up after a
706 for builder
in self
.botmaster
.builders
.values():
707 builder
.builder_status
.setLogCompressionLimit(logCompressionLimit
)
708 builder
.builder_status
.setLogCompressionMethod(logCompressionMethod
)
709 builder
.builder_status
.setLogMaxSize(logMaxSize
)
710 builder
.builder_status
.setLogMaxTailSize(logMaxTailSize
)
712 if mergeRequests
is not None:
713 self
.botmaster
.mergeRequests
= mergeRequests
714 if prioritizeBuilders
is not None:
715 self
.botmaster
.prioritizeBuilders
= prioritizeBuilders
717 self
.buildCacheSize
= buildCacheSize
718 self
.eventHorizon
= eventHorizon
719 self
.logHorizon
= logHorizon
720 self
.buildHorizon
= buildHorizon
722 # self.slaves: Disconnect any that were attached and removed from the
723 # list. Update self.checker with the new list of passwords, including
724 # debug/change/status.
725 d
.addCallback(lambda res
: self
.loadConfig_Slaves(slaves
))
729 self
.checker
.addUser("debug", debugPassword
)
730 self
.debugPassword
= debugPassword
733 if manhole
!= self
.manhole
:
736 # disownServiceParent may return a Deferred
737 d
.addCallback(lambda res
: self
.manhole
.disownServiceParent())
741 d
.addCallback(_remove
)
744 self
.manhole
= manhole
745 manhole
.setServiceParent(self
)
748 # add/remove self.botmaster.builders to match builders. The
749 # botmaster will handle startup/shutdown issues.
750 d
.addCallback(lambda res
: self
.loadConfig_Builders(builders
))
752 d
.addCallback(lambda res
: self
.loadConfig_status(status
))
754 # Schedulers are added after Builders in case they start right away
755 d
.addCallback(lambda res
: self
.loadConfig_Schedulers(schedulers
))
756 # and Sources go after Schedulers for the same reason
757 d
.addCallback(lambda res
: self
.loadConfig_Sources(change_sources
))
760 if self
.slavePortnum
!= slavePortnum
:
762 def closeSlavePort(res
):
763 d1
= self
.slavePort
.disownServiceParent()
764 self
.slavePort
= None
766 d
.addCallback(closeSlavePort
)
767 if slavePortnum
is not None:
768 def openSlavePort(res
):
769 self
.slavePort
= strports
.service(slavePortnum
,
771 self
.slavePort
.setServiceParent(self
)
772 d
.addCallback(openSlavePort
)
773 log
.msg("BuildMaster listening on port %s" % slavePortnum
)
774 self
.slavePortnum
= slavePortnum
776 log
.msg("configuration update started")
778 self
.readConfig
= True
779 log
.msg("configuration update complete")
781 d
.addCallback(lambda res
: self
.botmaster
.maybeStartAllBuilds())
785 basedir
= buildbot_dir
788 basedir
= os
.path
.join(PKGDATADIR
, 'buildbot')
790 basedir
= os
.path
.join(SRCDIR
, 'buildbot')
792 if not os
.path
.exists(os
.path
.join(basedir
, 'builddir')):
793 os
.makedirs(os
.path
.join(basedir
, 'builddir'))
794 master_cfg_path
= mastercfgfile
796 JhBuildMaster(basedir
, master_cfg_path
).setServiceParent(application
)
798 JhBuildbotApplicationRunner
.application
= application
799 JhBuildbotApplicationRunner(options
).run()
801 def stop(self
, config
, pidfile
):
803 pid
= int(file(pidfile
).read())
805 raise FatalError(_('failed to get buildbot PID'))
807 os
.kill(pid
, signal
.SIGTERM
)
809 def reload_server_config(self
, config
, pidfile
):
811 pid
= int(file(pidfile
).read())
813 raise FatalError(_('failed to get buildbot PID'))
815 os
.kill(pid
, signal
.SIGHUP
)
818 register_command(cmd_bot
)