runner: increase start/stop timeout to 10 seconds, for #68
[buildbot.git] / buildbot / scripts / runner.py
blob0e2b27daadd02e3fc09e6ff512c718513366e99a
1 # -*- test-case-name: buildbot.test.test_runner -*-
3 # N.B.: don't import anything that might pull in a reactor yet. Some of our
4 # subcommands want to load modules that need the gtk reactor.
5 import os, sys, stat, re, time
6 from twisted.python import usage, util, runtime
8 # this is mostly just a front-end for mktap, twistd, and kill(1), but in the
9 # future it will also provide an interface to some developer tools that talk
10 # directly to a remote buildmaster (like 'try' and a status client)
12 # the create/start/stop commands should all be run as the same user,
13 # preferably a separate 'buildbot' account.
15 class MakerBase(usage.Options):
16 optFlags = [
17 ['help', 'h', "Display this message"],
18 ["quiet", "q", "Do not emit the commands being run"],
21 #["basedir", "d", None, "Base directory for the buildmaster"],
22 opt_h = usage.Options.opt_help
24 def parseArgs(self, *args):
25 if len(args) > 0:
26 self['basedir'] = args[0]
27 else:
28 self['basedir'] = None
29 if len(args) > 1:
30 raise usage.UsageError("I wasn't expecting so many arguments")
32 def postOptions(self):
33 if self['basedir'] is None:
34 raise usage.UsageError("<basedir> parameter is required")
35 self['basedir'] = os.path.abspath(self['basedir'])
37 makefile_sample = """# -*- makefile -*-
39 # This is a simple makefile which lives in a buildmaster/buildslave
40 # directory (next to the buildbot.tac file). It allows you to start/stop the
41 # master or slave by doing 'make start' or 'make stop'.
43 # The 'reconfig' target will tell a buildmaster to reload its config file.
45 start:
46 twistd --no_save -y buildbot.tac
48 stop:
49 kill `cat twistd.pid`
51 reconfig:
52 kill -HUP `cat twistd.pid`
54 log:
55 tail -f twistd.log
56 """
58 class Maker:
59 def __init__(self, config):
60 self.config = config
61 self.basedir = config['basedir']
62 self.force = config.get('force', False)
63 self.quiet = config['quiet']
65 def mkdir(self):
66 if os.path.exists(self.basedir):
67 if not self.quiet:
68 print "updating existing installation"
69 return
70 if not self.quiet: print "mkdir", self.basedir
71 os.mkdir(self.basedir)
73 def mkinfo(self):
74 path = os.path.join(self.basedir, "info")
75 if not os.path.exists(path):
76 if not self.quiet: print "mkdir", path
77 os.mkdir(path)
78 created = False
79 admin = os.path.join(path, "admin")
80 if not os.path.exists(admin):
81 if not self.quiet:
82 print "Creating info/admin, you need to edit it appropriately"
83 f = open(admin, "wt")
84 f.write("Your Name Here <admin@youraddress.invalid>\n")
85 f.close()
86 created = True
87 host = os.path.join(path, "host")
88 if not os.path.exists(host):
89 if not self.quiet:
90 print "Creating info/host, you need to edit it appropriately"
91 f = open(host, "wt")
92 f.write("Please put a description of this build host here\n")
93 f.close()
94 created = True
95 if created and not self.quiet:
96 print "Please edit the files in %s appropriately." % path
98 def chdir(self):
99 if not self.quiet: print "chdir", self.basedir
100 os.chdir(self.basedir)
102 def makeTAC(self, contents, secret=False):
103 tacfile = "buildbot.tac"
104 if os.path.exists(tacfile):
105 oldcontents = open(tacfile, "rt").read()
106 if oldcontents == contents:
107 if not self.quiet:
108 print "buildbot.tac already exists and is correct"
109 return
110 if not self.quiet:
111 print "not touching existing buildbot.tac"
112 print "creating buildbot.tac.new instead"
113 tacfile = "buildbot.tac.new"
114 f = open(tacfile, "wt")
115 f.write(contents)
116 f.close()
117 if secret:
118 os.chmod(tacfile, 0600)
120 def makefile(self):
121 target = "Makefile.sample"
122 if os.path.exists(target):
123 oldcontents = open(target, "rt").read()
124 if oldcontents == makefile_sample:
125 if not self.quiet:
126 print "Makefile.sample already exists and is correct"
127 return
128 if not self.quiet:
129 print "replacing Makefile.sample"
130 else:
131 if not self.quiet:
132 print "creating Makefile.sample"
133 f = open(target, "wt")
134 f.write(makefile_sample)
135 f.close()
137 def sampleconfig(self, source):
138 target = "master.cfg.sample"
139 config_sample = open(source, "rt").read()
140 if os.path.exists(target):
141 oldcontents = open(target, "rt").read()
142 if oldcontents == config_sample:
143 if not self.quiet:
144 print "master.cfg.sample already exists and is up-to-date"
145 return
146 if not self.quiet:
147 print "replacing master.cfg.sample"
148 else:
149 if not self.quiet:
150 print "creating master.cfg.sample"
151 f = open(target, "wt")
152 f.write(config_sample)
153 f.close()
154 os.chmod(target, 0600)
156 def public_html(self, index_html, buildbot_css, robots_txt,
157 repopulate=False):
158 webdir = os.path.join(self.basedir, "public_html")
159 if os.path.exists(webdir):
160 if not repopulate:
161 if not self.quiet:
162 print "public_html/ already exists: not replacing"
163 return
164 else:
165 os.mkdir(webdir)
166 if not self.quiet:
167 print "populating public_html/"
168 target = os.path.join(webdir, "index.html")
169 f = open(target, "wt")
170 f.write(open(index_html, "rt").read())
171 f.close()
173 target = os.path.join(webdir, "buildbot.css")
174 f = open(target, "wt")
175 f.write(open(buildbot_css, "rt").read())
176 f.close()
178 target = os.path.join(webdir, "robots.txt")
179 f = open(target, "wt")
180 f.write(open(robots_txt, "rt").read())
181 f.close()
184 class UpgradeMasterOptions(MakerBase):
185 optFlags = [
186 ["replace", "r", "Replace any modified files without confirmation."],
189 def getSynopsis(self):
190 return "Usage: buildbot upgrade-master [options] <basedir>"
192 longdesc = """
193 This command takes an existing buildmaster working directory and
194 adds/modifies the files there to work with the current version of
195 buildbot. When this command is finished, the buildmaster directory should
196 look much like a brand-new one created by the 'create-master' command.
198 Use this after you've upgraded your buildbot installation and before you
199 restart the buildmaster to use the new version.
201 If you have modified the files in your working directory, this command
202 will leave them untouched, but will put the new recommended contents in a
203 .new file (for example, if index.html has been modified, this command
204 will create index.html.new). You can then look at the new version and
205 decide how to merge its contents into your modified file.
208 def upgradeMaster(config):
209 basedir = config['basedir']
210 m = Maker(config)
211 m.quiet = True
212 # check TAC file
213 # check sample.cfg
214 # check web files: index.html, classic.css, robots.txt
215 webdir = os.path.join(basedir, "public_html")
216 m.public_html(util.sibpath(__file__, "../status/web/index.html"),
217 util.sibpath(__file__, "../status/web/classic.css"),
218 util.sibpath(__file__, "../status/web/robots.txt"),
219 repopulate=True
221 # check Makefile
223 class MasterOptions(MakerBase):
224 optFlags = [
225 ["force", "f",
226 "Re-use an existing directory (will not overwrite master.cfg file)"],
228 optParameters = [
229 ["config", "c", "master.cfg", "name of the buildmaster config file"],
231 def getSynopsis(self):
232 return "Usage: buildbot create-master [options] <basedir>"
234 longdesc = """
235 This command creates a buildmaster working directory and buildbot.tac
236 file. The master will live in <dir> and create various files there.
238 At runtime, the master will read a configuration file (named
239 'master.cfg' by default) in its basedir. This file should contain python
240 code which eventually defines a dictionary named 'BuildmasterConfig'.
241 The elements of this dictionary are used to configure the Buildmaster.
242 See doc/config.xhtml for details about what can be controlled through
243 this interface."""
245 masterTAC = """
246 from twisted.application import service
247 from buildbot.master import BuildMaster
249 basedir = r'%(basedir)s'
250 configfile = r'%(config)s'
252 application = service.Application('buildmaster')
253 BuildMaster(basedir, configfile).setServiceParent(application)
257 def createMaster(config):
258 m = Maker(config)
259 m.mkdir()
260 m.chdir()
261 contents = masterTAC % config
262 m.makeTAC(contents)
263 m.sampleconfig(util.sibpath(__file__, "sample.cfg"))
264 m.public_html(util.sibpath(__file__, "../status/web/index.html"),
265 util.sibpath(__file__, "../status/web/classic.css"),
266 util.sibpath(__file__, "../status/web/robots.txt"),
268 m.makefile()
270 if not m.quiet: print "buildmaster configured in %s" % m.basedir
272 class SlaveOptions(MakerBase):
273 optFlags = [
274 ["force", "f", "Re-use an existing directory"],
276 optParameters = [
277 # ["name", "n", None, "Name for this build slave"],
278 # ["passwd", "p", None, "Password for this build slave"],
279 # ["basedir", "d", ".", "Base directory to use"],
280 # ["master", "m", "localhost:8007",
281 # "Location of the buildmaster (host:port)"],
283 ["keepalive", "k", 600,
284 "Interval at which keepalives should be sent (in seconds)"],
285 ["usepty", None, 1,
286 "(1 or 0) child processes should be run in a pty"],
287 ["umask", None, "None",
288 "controls permissions of generated files. Use --umask=022 to be world-readable"],
291 longdesc = """
292 This command creates a buildslave working directory and buildbot.tac
293 file. The bot will use the <name> and <passwd> arguments to authenticate
294 itself when connecting to the master. All commands are run in a
295 build-specific subdirectory of <basedir>. <master> is a string of the
296 form 'hostname:port', and specifies where the buildmaster can be reached.
298 <name>, <passwd>, and <master> will be provided by the buildmaster
299 administrator for your bot. You must choose <basedir> yourself.
302 def getSynopsis(self):
303 return "Usage: buildbot create-slave [options] <basedir> <master> <name> <passwd>"
305 def parseArgs(self, *args):
306 if len(args) < 4:
307 raise usage.UsageError("command needs more arguments")
308 basedir, master, name, passwd = args
309 self['basedir'] = basedir
310 self['master'] = master
311 self['name'] = name
312 self['passwd'] = passwd
314 def postOptions(self):
315 MakerBase.postOptions(self)
316 self['usepty'] = int(self['usepty'])
317 self['keepalive'] = int(self['keepalive'])
318 if self['master'].find(":") == -1:
319 raise usage.UsageError("--master must be in the form host:portnum")
321 slaveTAC = """
322 from twisted.application import service
323 from buildbot.slave.bot import BuildSlave
325 basedir = r'%(basedir)s'
326 buildmaster_host = '%(host)s'
327 port = %(port)d
328 slavename = '%(name)s'
329 passwd = '%(passwd)s'
330 keepalive = %(keepalive)d
331 usepty = %(usepty)d
332 umask = %(umask)s
334 application = service.Application('buildslave')
335 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir,
336 keepalive, usepty, umask=umask)
337 s.setServiceParent(application)
341 def createSlave(config):
342 m = Maker(config)
343 m.mkdir()
344 m.chdir()
345 try:
346 master = config['master']
347 host, port = re.search(r'(.+):(\d+)', master).groups()
348 config['host'] = host
349 config['port'] = int(port)
350 except:
351 print "unparseable master location '%s'" % master
352 print " expecting something more like localhost:8007"
353 raise
354 contents = slaveTAC % config
356 m.makeTAC(contents, secret=True)
358 m.makefile()
359 m.mkinfo()
361 if not m.quiet: print "buildslave configured in %s" % m.basedir
365 def stop(config, signame="TERM", wait=False):
366 import signal
367 basedir = config['basedir']
368 quiet = config['quiet']
369 os.chdir(basedir)
370 f = open("twistd.pid", "rt")
371 pid = int(f.read().strip())
372 signum = getattr(signal, "SIG"+signame)
373 timer = 0
374 os.kill(pid, signum)
375 if not wait:
376 if not quiet:
377 print "sent SIG%s to process" % signame
378 return
379 time.sleep(0.1)
380 while timer < 10:
381 # poll once per second until twistd.pid goes away, up to 10 seconds
382 try:
383 os.kill(pid, 0)
384 except OSError:
385 if not quiet:
386 print "buildbot process %d is dead" % pid
387 return
388 timer += 1
389 time.sleep(1)
390 if not quiet:
391 print "never saw process go away"
393 def restart(config):
394 quiet = config['quiet']
395 from buildbot.scripts.startup import start
396 stop(config, wait=True)
397 if not quiet:
398 print "now restarting buildbot process.."
399 start(config)
402 def loadOptions(filename="options", here=None, home=None):
403 """Find the .buildbot/FILENAME file. Crawl from the current directory up
404 towards the root, and also look in ~/.buildbot . The first directory
405 that's owned by the user and has the file we're looking for wins. Windows
406 skips the owned-by-user test.
408 @rtype: dict
409 @return: a dictionary of names defined in the options file. If no options
410 file was found, return an empty dict.
413 if here is None:
414 here = os.getcwd()
415 here = os.path.abspath(here)
417 if home is None:
418 if runtime.platformType == 'win32':
419 home = os.path.join(os.environ['APPDATA'], "buildbot")
420 else:
421 home = os.path.expanduser("~/.buildbot")
423 searchpath = []
424 toomany = 20
425 while True:
426 searchpath.append(os.path.join(here, ".buildbot"))
427 next = os.path.dirname(here)
428 if next == here:
429 break # we've hit the root
430 here = next
431 toomany -= 1 # just in case
432 if toomany == 0:
433 raise ValueError("Hey, I seem to have wandered up into the "
434 "infinite glories of the heavens. Oops.")
435 searchpath.append(home)
437 localDict = {}
439 for d in searchpath:
440 if os.path.isdir(d):
441 if runtime.platformType != 'win32':
442 if os.stat(d)[stat.ST_UID] != os.getuid():
443 print "skipping %s because you don't own it" % d
444 continue # security, skip other people's directories
445 optfile = os.path.join(d, filename)
446 if os.path.exists(optfile):
447 try:
448 f = open(optfile, "r")
449 options = f.read()
450 exec options in localDict
451 except:
452 print "error while reading %s" % optfile
453 raise
454 break
456 for k in localDict.keys():
457 if k.startswith("__"):
458 del localDict[k]
459 return localDict
461 class StartOptions(MakerBase):
462 optFlags = [
463 ['quiet', 'q', "Don't display startup log messages"],
465 def getSynopsis(self):
466 return "Usage: buildbot start <basedir>"
468 class StopOptions(MakerBase):
469 def getSynopsis(self):
470 return "Usage: buildbot stop <basedir>"
472 class ReconfigOptions(MakerBase):
473 optFlags = [
474 ['quiet', 'q', "Don't display log messages about reconfiguration"],
476 def getSynopsis(self):
477 return "Usage: buildbot reconfig <basedir>"
481 class RestartOptions(MakerBase):
482 optFlags = [
483 ['quiet', 'q', "Don't display startup log messages"],
485 def getSynopsis(self):
486 return "Usage: buildbot restart <basedir>"
488 class DebugClientOptions(usage.Options):
489 optFlags = [
490 ['help', 'h', "Display this message"],
492 optParameters = [
493 ["master", "m", None,
494 "Location of the buildmaster's slaveport (host:port)"],
495 ["passwd", "p", None, "Debug password to use"],
498 def parseArgs(self, *args):
499 if len(args) > 0:
500 self['master'] = args[0]
501 if len(args) > 1:
502 self['passwd'] = args[1]
503 if len(args) > 2:
504 raise usage.UsageError("I wasn't expecting so many arguments")
506 def debugclient(config):
507 from buildbot.clients import debug
508 opts = loadOptions()
510 master = config.get('master')
511 if not master:
512 master = opts.get('master')
513 if master is None:
514 raise usage.UsageError("master must be specified: on the command "
515 "line or in ~/.buildbot/options")
517 passwd = config.get('passwd')
518 if not passwd:
519 passwd = opts.get('debugPassword')
520 if passwd is None:
521 raise usage.UsageError("passwd must be specified: on the command "
522 "line or in ~/.buildbot/options")
524 d = debug.DebugWidget(master, passwd)
525 d.run()
527 class StatusClientOptions(usage.Options):
528 optFlags = [
529 ['help', 'h', "Display this message"],
531 optParameters = [
532 ["master", "m", None,
533 "Location of the buildmaster's status port (host:port)"],
536 def parseArgs(self, *args):
537 if len(args) > 0:
538 self['master'] = args[0]
539 if len(args) > 1:
540 raise usage.UsageError("I wasn't expecting so many arguments")
542 def statuslog(config):
543 from buildbot.clients import base
544 opts = loadOptions()
545 master = config.get('master')
546 if not master:
547 master = opts.get('masterstatus')
548 if master is None:
549 raise usage.UsageError("master must be specified: on the command "
550 "line or in ~/.buildbot/options")
551 c = base.TextClient(master)
552 c.run()
554 def statusgui(config):
555 from buildbot.clients import gtkPanes
556 opts = loadOptions()
557 master = config.get('master')
558 if not master:
559 master = opts.get('masterstatus')
560 if master is None:
561 raise usage.UsageError("master must be specified: on the command "
562 "line or in ~/.buildbot/options")
563 c = gtkPanes.GtkClient(master)
564 c.run()
566 class SendChangeOptions(usage.Options):
567 optParameters = [
568 ("master", "m", None,
569 "Location of the buildmaster's PBListener (host:port)"),
570 ("username", "u", None, "Username performing the commit"),
571 ("branch", "b", None, "Branch specifier"),
572 ("revision", "r", None, "Revision specifier (string)"),
573 ("revision_number", "n", None, "Revision specifier (integer)"),
574 ("revision_file", None, None, "Filename containing revision spec"),
575 ("comments", "m", None, "log message"),
576 ("logfile", "F", None,
577 "Read the log messages from this file (- for stdin)"),
579 def getSynopsis(self):
580 return "Usage: buildbot sendchange [options] filenames.."
581 def parseArgs(self, *args):
582 self['files'] = args
585 def sendchange(config, runReactor=False):
586 """Send a single change to the buildmaster's PBChangeSource. The
587 connection will be drpoped as soon as the Change has been sent."""
588 from buildbot.clients.sendchange import Sender
590 opts = loadOptions()
591 user = config.get('username', opts.get('username'))
592 master = config.get('master', opts.get('master'))
593 branch = config.get('branch', opts.get('branch'))
594 revision = config.get('revision')
595 # SVN and P4 use numeric revisions
596 if config.get("revision_number"):
597 revision = int(config['revision_number'])
598 if config.get("revision_file"):
599 revision = open(config["revision_file"],"r").read()
601 comments = config.get('comments')
602 if not comments and config.get('logfile'):
603 if config['logfile'] == "-":
604 f = sys.stdin
605 else:
606 f = open(config['logfile'], "rt")
607 comments = f.read()
608 if comments is None:
609 comments = ""
611 files = config.get('files', [])
613 assert user, "you must provide a username"
614 assert master, "you must provide the master location"
616 s = Sender(master, user)
617 d = s.send(branch, revision, comments, files)
618 if runReactor:
619 d.addCallbacks(s.printSuccess, s.printFailure)
620 d.addBoth(s.stop)
621 s.run()
622 return d
625 class ForceOptions(usage.Options):
626 optParameters = [
627 ["builder", None, None, "which Builder to start"],
628 ["branch", None, None, "which branch to build"],
629 ["revision", None, None, "which revision to build"],
630 ["reason", None, None, "the reason for starting the build"],
633 def parseArgs(self, *args):
634 args = list(args)
635 if len(args) > 0:
636 if self['builder'] is not None:
637 raise usage.UsageError("--builder provided in two ways")
638 self['builder'] = args.pop(0)
639 if len(args) > 0:
640 if self['reason'] is not None:
641 raise usage.UsageError("--reason provided in two ways")
642 self['reason'] = " ".join(args)
645 class TryOptions(usage.Options):
646 optParameters = [
647 ["connect", "c", None,
648 "how to reach the buildmaster, either 'ssh' or 'pb'"],
649 # for ssh, use --tryhost, --username, and --trydir
650 ["tryhost", None, None,
651 "the hostname (used by ssh) for the buildmaster"],
652 ["trydir", None, None,
653 "the directory (on the tryhost) where tryjobs are deposited"],
654 ["username", "u", None, "Username performing the trial build"],
655 # for PB, use --master, --username, and --passwd
656 ["master", "m", None,
657 "Location of the buildmaster's PBListener (host:port)"],
658 ["passwd", None, None, "password for PB authentication"],
660 ["diff", None, None,
661 "Filename of a patch to use instead of scanning a local tree. Use '-' for stdin."],
662 ["patchlevel", "p", 0,
663 "Number of slashes to remove from patch pathnames, like the -p option to 'patch'"],
665 ["baserev", None, None,
666 "Base revision to use instead of scanning a local tree."],
668 ["vc", None, None,
669 "The VC system in use, one of: cvs,svn,tla,baz,darcs"],
670 ["branch", None, None,
671 "The branch in use, for VC systems that can't figure it out"
672 " themselves"],
674 ["builder", "b", None,
675 "Run the trial build on this Builder. Can be used multiple times."],
678 optFlags = [
679 ["wait", None, "wait until the builds have finished"],
682 def __init__(self):
683 super(TryOptions, self).__init__()
684 self['builders'] = []
686 def opt_builder(self, option):
687 self['builders'].append(option)
689 def opt_patchlevel(self, option):
690 self['patchlevel'] = int(option)
692 def getSynopsis(self):
693 return "Usage: buildbot try [options]"
695 def doTry(config):
696 from buildbot.scripts import tryclient
697 t = tryclient.Try(config)
698 t.run()
700 class TryServerOptions(usage.Options):
701 optParameters = [
702 ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"],
705 def doTryServer(config):
706 import md5
707 jobdir = os.path.expanduser(config["jobdir"])
708 job = sys.stdin.read()
709 # now do a 'safecat'-style write to jobdir/tmp, then move atomically to
710 # jobdir/new . Rather than come up with a unique name randomly, I'm just
711 # going to MD5 the contents and prepend a timestamp.
712 timestring = "%d" % time.time()
713 jobhash = md5.new(job).hexdigest()
714 fn = "%s-%s" % (timestring, jobhash)
715 tmpfile = os.path.join(jobdir, "tmp", fn)
716 newfile = os.path.join(jobdir, "new", fn)
717 f = open(tmpfile, "w")
718 f.write(job)
719 f.close()
720 os.rename(tmpfile, newfile)
723 class Options(usage.Options):
724 synopsis = "Usage: buildbot <command> [command options]"
726 subCommands = [
727 # the following are all admin commands
728 ['create-master', None, MasterOptions,
729 "Create and populate a directory for a new buildmaster"],
730 ['upgrade-master', None, UpgradeMasterOptions,
731 "Upgrade an existing buildmaster directory for the current version"],
732 ['create-slave', None, SlaveOptions,
733 "Create and populate a directory for a new buildslave"],
734 ['start', None, StartOptions, "Start a buildmaster or buildslave"],
735 ['stop', None, StopOptions, "Stop a buildmaster or buildslave"],
736 ['restart', None, RestartOptions,
737 "Restart a buildmaster or buildslave"],
739 ['reconfig', None, ReconfigOptions,
740 "SIGHUP a buildmaster to make it re-read the config file"],
741 ['sighup', None, ReconfigOptions,
742 "SIGHUP a buildmaster to make it re-read the config file"],
744 ['sendchange', None, SendChangeOptions,
745 "Send a change to the buildmaster"],
747 ['debugclient', None, DebugClientOptions,
748 "Launch a small debug panel GUI"],
750 ['statuslog', None, StatusClientOptions,
751 "Emit current builder status to stdout"],
752 ['statusgui', None, StatusClientOptions,
753 "Display a small window showing current builder status"],
755 #['force', None, ForceOptions, "Run a build"],
756 ['try', None, TryOptions, "Run a build with your local changes"],
758 ['tryserver', None, TryServerOptions,
759 "buildmaster-side 'try' support function, not for users"],
761 # TODO: 'watch'
764 def opt_version(self):
765 import buildbot
766 print "Buildbot version: %s" % buildbot.version
767 usage.Options.opt_version(self)
769 def opt_verbose(self):
770 from twisted.python import log
771 log.startLogging(sys.stderr)
773 def postOptions(self):
774 if not hasattr(self, 'subOptions'):
775 raise usage.UsageError("must specify a command")
778 def run():
779 config = Options()
780 try:
781 config.parseOptions()
782 except usage.error, e:
783 print "%s: %s" % (sys.argv[0], e)
784 print
785 c = getattr(config, 'subOptions', config)
786 print str(c)
787 sys.exit(1)
789 command = config.subCommand
790 so = config.subOptions
792 if command == "create-master":
793 createMaster(so)
794 elif command == "upgrade-master":
795 upgradeMaster(so)
796 elif command == "create-slave":
797 createSlave(so)
798 elif command == "start":
799 from buildbot.scripts.startup import start
800 start(so)
801 elif command == "stop":
802 stop(so, wait=True)
803 elif command == "restart":
804 restart(so)
805 elif command == "reconfig" or command == "sighup":
806 from buildbot.scripts.reconfig import Reconfigurator
807 Reconfigurator().run(so)
808 elif command == "sendchange":
809 sendchange(so, True)
810 elif command == "debugclient":
811 debugclient(so)
812 elif command == "statuslog":
813 statuslog(so)
814 elif command == "statusgui":
815 statusgui(so)
816 elif command == "try":
817 doTry(so)
818 elif command == "tryserver":
819 doTryServer(so)