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
7 from twisted
.python
import usage
, util
, runtime
9 from buildbot
.interfaces
import BuildbotNotRunningError
11 # this is mostly just a front-end for mktap, twistd, and kill(1), but in the
12 # future it will also provide an interface to some developer tools that talk
13 # directly to a remote buildmaster (like 'try' and a status client)
15 # the create/start/stop commands should all be run as the same user,
16 # preferably a separate 'buildbot' account.
18 class MakerBase(usage
.Options
):
20 ['help', 'h', "Display this message"],
21 ["quiet", "q", "Do not emit the commands being run"],
24 #["basedir", "d", None, "Base directory for the buildmaster"],
25 opt_h
= usage
.Options
.opt_help
27 def parseArgs(self
, *args
):
29 self
['basedir'] = args
[0]
31 self
['basedir'] = None
33 raise usage
.UsageError("I wasn't expecting so many arguments")
35 def postOptions(self
):
36 if self
['basedir'] is None:
37 raise usage
.UsageError("<basedir> parameter is required")
38 self
['basedir'] = os
.path
.abspath(self
['basedir'])
40 makefile_sample
= """# -*- makefile -*-
42 # This is a simple makefile which lives in a buildmaster/buildslave
43 # directory (next to the buildbot.tac file). It allows you to start/stop the
44 # master or slave by doing 'make start' or 'make stop'.
46 # The 'reconfig' target will tell a buildmaster to reload its config file.
49 twistd --no_save -y buildbot.tac
55 kill -HUP `cat twistd.pid`
62 def __init__(self
, config
):
64 self
.basedir
= config
['basedir']
65 self
.force
= config
.get('force', False)
66 self
.quiet
= config
['quiet']
69 if os
.path
.exists(self
.basedir
):
71 print "updating existing installation"
73 if not self
.quiet
: print "mkdir", self
.basedir
74 os
.mkdir(self
.basedir
)
77 path
= os
.path
.join(self
.basedir
, "info")
78 if not os
.path
.exists(path
):
79 if not self
.quiet
: print "mkdir", path
82 admin
= os
.path
.join(path
, "admin")
83 if not os
.path
.exists(admin
):
85 print "Creating info/admin, you need to edit it appropriately"
87 f
.write("Your Name Here <admin@youraddress.invalid>\n")
90 host
= os
.path
.join(path
, "host")
91 if not os
.path
.exists(host
):
93 print "Creating info/host, you need to edit it appropriately"
95 f
.write("Please put a description of this build host here\n")
98 if created
and not self
.quiet
:
99 print "Please edit the files in %s appropriately." % path
102 if not self
.quiet
: print "chdir", self
.basedir
103 os
.chdir(self
.basedir
)
105 def makeTAC(self
, contents
, secret
=False):
106 tacfile
= "buildbot.tac"
107 if os
.path
.exists(tacfile
):
108 oldcontents
= open(tacfile
, "rt").read()
109 if oldcontents
== contents
:
111 print "buildbot.tac already exists and is correct"
114 print "not touching existing buildbot.tac"
115 print "creating buildbot.tac.new instead"
116 tacfile
= "buildbot.tac.new"
117 f
= open(tacfile
, "wt")
121 os
.chmod(tacfile
, 0600)
124 target
= "Makefile.sample"
125 if os
.path
.exists(target
):
126 oldcontents
= open(target
, "rt").read()
127 if oldcontents
== makefile_sample
:
129 print "Makefile.sample already exists and is correct"
132 print "replacing Makefile.sample"
135 print "creating Makefile.sample"
136 f
= open(target
, "wt")
137 f
.write(makefile_sample
)
140 def sampleconfig(self
, source
):
141 target
= "master.cfg.sample"
142 config_sample
= open(source
, "rt").read()
143 if os
.path
.exists(target
):
144 oldcontents
= open(target
, "rt").read()
145 if oldcontents
== config_sample
:
147 print "master.cfg.sample already exists and is up-to-date"
150 print "replacing master.cfg.sample"
153 print "creating master.cfg.sample"
154 f
= open(target
, "wt")
155 f
.write(config_sample
)
157 os
.chmod(target
, 0600)
159 def public_html(self
, index_html
, buildbot_css
, robots_txt
):
160 webdir
= os
.path
.join(self
.basedir
, "public_html")
161 if os
.path
.exists(webdir
):
163 print "public_html/ already exists: not replacing"
168 print "populating public_html/"
169 target
= os
.path
.join(webdir
, "index.html")
170 f
= open(target
, "wt")
171 f
.write(open(index_html
, "rt").read())
174 target
= os
.path
.join(webdir
, "buildbot.css")
175 f
= open(target
, "wt")
176 f
.write(open(buildbot_css
, "rt").read())
179 target
= os
.path
.join(webdir
, "robots.txt")
180 f
= open(target
, "wt")
181 f
.write(open(robots_txt
, "rt").read())
184 def populate_if_missing(self
, target
, source
, overwrite
=False):
185 new_contents
= open(source
, "rt").read()
186 if os
.path
.exists(target
):
187 old_contents
= open(target
, "rt").read()
188 if old_contents
!= new_contents
:
191 print "%s has old/modified contents" % target
192 print " overwriting it with new contents"
193 open(target
, "wt").write(new_contents
)
196 print "%s has old/modified contents" % target
197 print " writing new contents to %s.new" % target
198 open(target
+ ".new", "wt").write(new_contents
)
199 # otherwise, it's up to date
202 print "populating %s" % target
203 open(target
, "wt").write(new_contents
)
205 def upgrade_public_html(self
, index_html
, buildbot_css
, robots_txt
):
206 webdir
= os
.path
.join(self
.basedir
, "public_html")
207 if not os
.path
.exists(webdir
):
209 print "populating public_html/"
211 self
.populate_if_missing(os
.path
.join(webdir
, "index.html"),
213 self
.populate_if_missing(os
.path
.join(webdir
, "buildbot.css"),
215 self
.populate_if_missing(os
.path
.join(webdir
, "robots.txt"),
218 def check_master_cfg(self
):
219 from buildbot
.master
import BuildMaster
220 from twisted
.python
import log
, failure
222 master_cfg
= os
.path
.join(self
.basedir
, "master.cfg")
223 if not os
.path
.exists(master_cfg
):
225 print "No master.cfg found"
228 # side-effects of loading the config file:
230 # for each Builder defined in c['builders'], if the status directory
231 # didn't already exist, it will be created, and the
232 # $BUILDERNAME/builder pickle might be created (with a single
233 # "builder created" event).
235 # we put basedir in front of sys.path, because that's how the
236 # buildmaster itself will run, and it is quite common to have the
237 # buildmaster import helper classes from other .py files in its
240 if sys
.path
[0] != self
.basedir
:
241 sys
.path
.insert(0, self
.basedir
)
243 m
= BuildMaster(self
.basedir
)
244 # we need to route log.msg to stdout, so any problems can be seen
245 # there. But if everything goes well, I'd rather not clutter stdout
246 # with log messages. So instead we add a logObserver which gathers
247 # messages and only displays them if something goes wrong.
249 log
.addObserver(messages
.append
)
251 # this will raise an exception if there's something wrong with
252 # the config file. Note that this BuildMaster instance is never
253 # started, so it won't actually do anything with the
255 m
.loadConfig(open(master_cfg
, "r"))
257 f
= failure
.Failure()
261 print "".join(m
['message'])
264 print "An error was detected in the master.cfg file."
265 print "Please correct the problem and run 'buildbot upgrade-master' again."
270 class UpgradeMasterOptions(MakerBase
):
272 ["replace", "r", "Replace any modified files without confirmation."],
275 def getSynopsis(self
):
276 return "Usage: buildbot upgrade-master [options] <basedir>"
279 This command takes an existing buildmaster working directory and
280 adds/modifies the files there to work with the current version of
281 buildbot. When this command is finished, the buildmaster directory should
282 look much like a brand-new one created by the 'create-master' command.
284 Use this after you've upgraded your buildbot installation and before you
285 restart the buildmaster to use the new version.
287 If you have modified the files in your working directory, this command
288 will leave them untouched, but will put the new recommended contents in a
289 .new file (for example, if index.html has been modified, this command
290 will create index.html.new). You can then look at the new version and
291 decide how to merge its contents into your modified file.
294 def upgradeMaster(config
):
295 basedir
= config
['basedir']
297 # TODO: check Makefile
298 # TODO: check TAC file
299 # check web files: index.html, classic.css, robots.txt
300 webdir
= os
.path
.join(basedir
, "public_html")
301 m
.upgrade_public_html(util
.sibpath(__file__
, "../status/web/index.html"),
302 util
.sibpath(__file__
, "../status/web/classic.css"),
303 util
.sibpath(__file__
, "../status/web/robots.txt"),
305 m
.populate_if_missing(os
.path
.join(basedir
, "master.cfg.sample"),
306 util
.sibpath(__file__
, "sample.cfg"),
308 rc
= m
.check_master_cfg()
311 if not config
['quiet']:
312 print "upgrade complete"
315 class MasterOptions(MakerBase
):
318 "Re-use an existing directory (will not overwrite master.cfg file)"],
321 ["config", "c", "master.cfg", "name of the buildmaster config file"],
323 def getSynopsis(self
):
324 return "Usage: buildbot create-master [options] <basedir>"
327 This command creates a buildmaster working directory and buildbot.tac
328 file. The master will live in <dir> and create various files there.
330 At runtime, the master will read a configuration file (named
331 'master.cfg' by default) in its basedir. This file should contain python
332 code which eventually defines a dictionary named 'BuildmasterConfig'.
333 The elements of this dictionary are used to configure the Buildmaster.
334 See doc/config.xhtml for details about what can be controlled through
338 from twisted.application import service
339 from buildbot.master import BuildMaster
341 basedir = r'%(basedir)s'
342 configfile = r'%(config)s'
344 application = service.Application('buildmaster')
345 BuildMaster(basedir, configfile).setServiceParent(application)
349 def createMaster(config
):
353 contents
= masterTAC
% config
355 m
.sampleconfig(util
.sibpath(__file__
, "sample.cfg"))
356 m
.public_html(util
.sibpath(__file__
, "../status/web/index.html"),
357 util
.sibpath(__file__
, "../status/web/classic.css"),
358 util
.sibpath(__file__
, "../status/web/robots.txt"),
362 if not m
.quiet
: print "buildmaster configured in %s" % m
.basedir
364 class SlaveOptions(MakerBase
):
366 ["force", "f", "Re-use an existing directory"],
369 # ["name", "n", None, "Name for this build slave"],
370 # ["passwd", "p", None, "Password for this build slave"],
371 # ["basedir", "d", ".", "Base directory to use"],
372 # ["master", "m", "localhost:8007",
373 # "Location of the buildmaster (host:port)"],
375 ["keepalive", "k", 600,
376 "Interval at which keepalives should be sent (in seconds)"],
378 "(1 or 0) child processes should be run in a pty"],
379 ["umask", None, "None",
380 "controls permissions of generated files. Use --umask=022 to be world-readable"],
384 This command creates a buildslave working directory and buildbot.tac
385 file. The bot will use the <name> and <passwd> arguments to authenticate
386 itself when connecting to the master. All commands are run in a
387 build-specific subdirectory of <basedir>. <master> is a string of the
388 form 'hostname:port', and specifies where the buildmaster can be reached.
390 <name>, <passwd>, and <master> will be provided by the buildmaster
391 administrator for your bot. You must choose <basedir> yourself.
394 def getSynopsis(self
):
395 return "Usage: buildbot create-slave [options] <basedir> <master> <name> <passwd>"
397 def parseArgs(self
, *args
):
399 raise usage
.UsageError("command needs more arguments")
400 basedir
, master
, name
, passwd
= args
401 self
['basedir'] = basedir
402 self
['master'] = master
404 self
['passwd'] = passwd
406 def postOptions(self
):
407 MakerBase
.postOptions(self
)
408 self
['usepty'] = int(self
['usepty'])
409 self
['keepalive'] = int(self
['keepalive'])
410 if self
['master'].find(":") == -1:
411 raise usage
.UsageError("--master must be in the form host:portnum")
414 from twisted.application import service
415 from buildbot.slave.bot import BuildSlave
417 basedir = r'%(basedir)s'
418 buildmaster_host = '%(host)s'
420 slavename = '%(name)s'
421 passwd = '%(passwd)s'
422 keepalive = %(keepalive)d
426 application = service.Application('buildslave')
427 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir,
428 keepalive, usepty, umask=umask)
429 s.setServiceParent(application)
433 def createSlave(config
):
438 master
= config
['master']
439 host
, port
= re
.search(r
'(.+):(\d+)', master
).groups()
440 config
['host'] = host
441 config
['port'] = int(port
)
443 print "unparseable master location '%s'" % master
444 print " expecting something more like localhost:8007"
446 contents
= slaveTAC
% config
448 m
.makeTAC(contents
, secret
=True)
453 if not m
.quiet
: print "buildslave configured in %s" % m
.basedir
457 def stop(config
, signame
="TERM", wait
=False):
459 basedir
= config
['basedir']
460 quiet
= config
['quiet']
463 f
= open("twistd.pid", "rt")
465 raise BuildbotNotRunningError
466 pid
= int(f
.read().strip())
467 signum
= getattr(signal
, "SIG"+signame
)
472 print "sent SIG%s to process" % signame
476 # poll once per second until twistd.pid goes away, up to 10 seconds
481 print "buildbot process %d is dead" % pid
486 print "never saw process go away"
489 quiet
= config
['quiet']
490 from buildbot
.scripts
.startup
import start
492 stop(config
, wait
=True)
493 except BuildbotNotRunningError
:
496 print "now restarting buildbot process.."
500 def loadOptions(filename
="options", here
=None, home
=None):
501 """Find the .buildbot/FILENAME file. Crawl from the current directory up
502 towards the root, and also look in ~/.buildbot . The first directory
503 that's owned by the user and has the file we're looking for wins. Windows
504 skips the owned-by-user test.
507 @return: a dictionary of names defined in the options file. If no options
508 file was found, return an empty dict.
513 here
= os
.path
.abspath(here
)
516 if runtime
.platformType
== 'win32':
517 home
= os
.path
.join(os
.environ
['APPDATA'], "buildbot")
519 home
= os
.path
.expanduser("~/.buildbot")
524 searchpath
.append(os
.path
.join(here
, ".buildbot"))
525 next
= os
.path
.dirname(here
)
527 break # we've hit the root
529 toomany
-= 1 # just in case
531 raise ValueError("Hey, I seem to have wandered up into the "
532 "infinite glories of the heavens. Oops.")
533 searchpath
.append(home
)
539 if runtime
.platformType
!= 'win32':
540 if os
.stat(d
)[stat
.ST_UID
] != os
.getuid():
541 print "skipping %s because you don't own it" % d
542 continue # security, skip other people's directories
543 optfile
= os
.path
.join(d
, filename
)
544 if os
.path
.exists(optfile
):
546 f
= open(optfile
, "r")
548 exec options
in localDict
550 print "error while reading %s" % optfile
554 for k
in localDict
.keys():
555 if k
.startswith("__"):
559 class StartOptions(MakerBase
):
561 ['quiet', 'q', "Don't display startup log messages"],
563 def getSynopsis(self
):
564 return "Usage: buildbot start <basedir>"
566 class StopOptions(MakerBase
):
567 def getSynopsis(self
):
568 return "Usage: buildbot stop <basedir>"
570 class ReconfigOptions(MakerBase
):
572 ['quiet', 'q', "Don't display log messages about reconfiguration"],
574 def getSynopsis(self
):
575 return "Usage: buildbot reconfig <basedir>"
579 class RestartOptions(MakerBase
):
581 ['quiet', 'q', "Don't display startup log messages"],
583 def getSynopsis(self
):
584 return "Usage: buildbot restart <basedir>"
586 class DebugClientOptions(usage
.Options
):
588 ['help', 'h', "Display this message"],
591 ["master", "m", None,
592 "Location of the buildmaster's slaveport (host:port)"],
593 ["passwd", "p", None, "Debug password to use"],
596 def parseArgs(self
, *args
):
598 self
['master'] = args
[0]
600 self
['passwd'] = args
[1]
602 raise usage
.UsageError("I wasn't expecting so many arguments")
604 def debugclient(config
):
605 from buildbot
.clients
import debug
608 master
= config
.get('master')
610 master
= opts
.get('master')
612 raise usage
.UsageError("master must be specified: on the command "
613 "line or in ~/.buildbot/options")
615 passwd
= config
.get('passwd')
617 passwd
= opts
.get('debugPassword')
619 raise usage
.UsageError("passwd must be specified: on the command "
620 "line or in ~/.buildbot/options")
622 d
= debug
.DebugWidget(master
, passwd
)
625 class StatusClientOptions(usage
.Options
):
627 ['help', 'h', "Display this message"],
630 ["master", "m", None,
631 "Location of the buildmaster's status port (host:port)"],
634 def parseArgs(self
, *args
):
636 self
['master'] = args
[0]
638 raise usage
.UsageError("I wasn't expecting so many arguments")
640 def statuslog(config
):
641 from buildbot
.clients
import base
643 master
= config
.get('master')
645 master
= opts
.get('masterstatus')
647 raise usage
.UsageError("master must be specified: on the command "
648 "line or in ~/.buildbot/options")
649 c
= base
.TextClient(master
)
652 def statusgui(config
):
653 from buildbot
.clients
import gtkPanes
655 master
= config
.get('master')
657 master
= opts
.get('masterstatus')
659 raise usage
.UsageError("master must be specified: on the command "
660 "line or in ~/.buildbot/options")
661 c
= gtkPanes
.GtkClient(master
)
664 class SendChangeOptions(usage
.Options
):
666 ("master", "m", None,
667 "Location of the buildmaster's PBListener (host:port)"),
668 ("username", "u", None, "Username performing the commit"),
669 ("branch", "b", None, "Branch specifier"),
670 ("revision", "r", None, "Revision specifier (string)"),
671 ("revision_number", "n", None, "Revision specifier (integer)"),
672 ("revision_file", None, None, "Filename containing revision spec"),
673 ("comments", "m", None, "log message"),
674 ("logfile", "F", None,
675 "Read the log messages from this file (- for stdin)"),
677 def getSynopsis(self
):
678 return "Usage: buildbot sendchange [options] filenames.."
679 def parseArgs(self
, *args
):
683 def sendchange(config
, runReactor
=False):
684 """Send a single change to the buildmaster's PBChangeSource. The
685 connection will be drpoped as soon as the Change has been sent."""
686 from buildbot
.clients
.sendchange
import Sender
689 user
= config
.get('username', opts
.get('username'))
690 master
= config
.get('master', opts
.get('master'))
691 branch
= config
.get('branch', opts
.get('branch'))
692 revision
= config
.get('revision')
693 # SVN and P4 use numeric revisions
694 if config
.get("revision_number"):
695 revision
= int(config
['revision_number'])
696 if config
.get("revision_file"):
697 revision
= open(config
["revision_file"],"r").read()
699 comments
= config
.get('comments')
700 if not comments
and config
.get('logfile'):
701 if config
['logfile'] == "-":
704 f
= open(config
['logfile'], "rt")
709 files
= config
.get('files', [])
711 assert user
, "you must provide a username"
712 assert master
, "you must provide the master location"
714 s
= Sender(master
, user
)
715 d
= s
.send(branch
, revision
, comments
, files
)
717 d
.addCallbacks(s
.printSuccess
, s
.printFailure
)
723 class ForceOptions(usage
.Options
):
725 ["builder", None, None, "which Builder to start"],
726 ["branch", None, None, "which branch to build"],
727 ["revision", None, None, "which revision to build"],
728 ["reason", None, None, "the reason for starting the build"],
731 def parseArgs(self
, *args
):
734 if self
['builder'] is not None:
735 raise usage
.UsageError("--builder provided in two ways")
736 self
['builder'] = args
.pop(0)
738 if self
['reason'] is not None:
739 raise usage
.UsageError("--reason provided in two ways")
740 self
['reason'] = " ".join(args
)
743 class TryOptions(usage
.Options
):
745 ["connect", "c", None,
746 "how to reach the buildmaster, either 'ssh' or 'pb'"],
747 # for ssh, use --tryhost, --username, and --trydir
748 ["tryhost", None, None,
749 "the hostname (used by ssh) for the buildmaster"],
750 ["trydir", None, None,
751 "the directory (on the tryhost) where tryjobs are deposited"],
752 ["username", "u", None, "Username performing the trial build"],
753 # for PB, use --master, --username, and --passwd
754 ["master", "m", None,
755 "Location of the buildmaster's PBListener (host:port)"],
756 ["passwd", None, None, "password for PB authentication"],
759 "Filename of a patch to use instead of scanning a local tree. Use '-' for stdin."],
760 ["patchlevel", "p", 0,
761 "Number of slashes to remove from patch pathnames, like the -p option to 'patch'"],
763 ["baserev", None, None,
764 "Base revision to use instead of scanning a local tree."],
767 "The VC system in use, one of: cvs,svn,tla,baz,darcs"],
768 ["branch", None, None,
769 "The branch in use, for VC systems that can't figure it out"
772 ["builder", "b", None,
773 "Run the trial build on this Builder. Can be used multiple times."],
774 ["properties", None, None,
775 "A set of properties made available in the build environment, format:prop=value,propb=valueb..."],
779 ["wait", None, "wait until the builds have finished"],
783 super(TryOptions
, self
).__init
__()
784 self
['builders'] = []
785 self
['properties'] = {}
787 def opt_builder(self
, option
):
788 self
['builders'].append(option
)
790 def opt_properties(self
, option
):
791 # We need to split the value of this option into a dictionary of properties
793 propertylist
= option
.split(",")
794 for i
in range(0,len(propertylist
)):
795 print propertylist
[i
]
796 splitproperty
= propertylist
[i
].split("=")
797 properties
[splitproperty
[0]] = splitproperty
[1]
798 self
['properties'] = properties
800 def opt_patchlevel(self
, option
):
801 self
['patchlevel'] = int(option
)
803 def getSynopsis(self
):
804 return "Usage: buildbot try [options]"
807 from buildbot
.scripts
import tryclient
808 t
= tryclient
.Try(config
)
811 class TryServerOptions(usage
.Options
):
813 ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"],
816 def doTryServer(config
):
818 jobdir
= os
.path
.expanduser(config
["jobdir"])
819 job
= sys
.stdin
.read()
820 # now do a 'safecat'-style write to jobdir/tmp, then move atomically to
821 # jobdir/new . Rather than come up with a unique name randomly, I'm just
822 # going to MD5 the contents and prepend a timestamp.
823 timestring
= "%d" % time
.time()
824 jobhash
= md5
.new(job
).hexdigest()
825 fn
= "%s-%s" % (timestring
, jobhash
)
826 tmpfile
= os
.path
.join(jobdir
, "tmp", fn
)
827 newfile
= os
.path
.join(jobdir
, "new", fn
)
828 f
= open(tmpfile
, "w")
831 os
.rename(tmpfile
, newfile
)
834 class CheckConfigOptions(usage
.Options
):
836 ['quiet', 'q', "Don't display error messages or tracebacks"],
839 def getSynopsis(self
):
840 return "Usage :buildbot checkconfig [configFile]\n" + \
841 " If not specified, 'master.cfg' will be used as 'configFile'"
843 def parseArgs(self
, *args
):
845 self
['configFile'] = args
[0]
847 self
['configFile'] = 'master.cfg'
850 def doCheckConfig(config
):
851 quiet
= config
.get('quiet')
852 configFile
= config
.get('configFile')
854 from buildbot
.scripts
.checkconfig
import ConfigLoader
855 ConfigLoader(configFile
)
858 # Print out the traceback in a nice format
859 t
, v
, tb
= sys
.exc_info()
860 traceback
.print_exception(t
, v
, tb
)
864 print "Config file is good!"
867 class Options(usage
.Options
):
868 synopsis
= "Usage: buildbot <command> [command options]"
871 # the following are all admin commands
872 ['create-master', None, MasterOptions
,
873 "Create and populate a directory for a new buildmaster"],
874 ['upgrade-master', None, UpgradeMasterOptions
,
875 "Upgrade an existing buildmaster directory for the current version"],
876 ['create-slave', None, SlaveOptions
,
877 "Create and populate a directory for a new buildslave"],
878 ['start', None, StartOptions
, "Start a buildmaster or buildslave"],
879 ['stop', None, StopOptions
, "Stop a buildmaster or buildslave"],
880 ['restart', None, RestartOptions
,
881 "Restart a buildmaster or buildslave"],
883 ['reconfig', None, ReconfigOptions
,
884 "SIGHUP a buildmaster to make it re-read the config file"],
885 ['sighup', None, ReconfigOptions
,
886 "SIGHUP a buildmaster to make it re-read the config file"],
888 ['sendchange', None, SendChangeOptions
,
889 "Send a change to the buildmaster"],
891 ['debugclient', None, DebugClientOptions
,
892 "Launch a small debug panel GUI"],
894 ['statuslog', None, StatusClientOptions
,
895 "Emit current builder status to stdout"],
896 ['statusgui', None, StatusClientOptions
,
897 "Display a small window showing current builder status"],
899 #['force', None, ForceOptions, "Run a build"],
900 ['try', None, TryOptions
, "Run a build with your local changes"],
902 ['tryserver', None, TryServerOptions
,
903 "buildmaster-side 'try' support function, not for users"],
905 ['checkconfig', None, CheckConfigOptions
,
906 "test the validity of a master.cfg config file"],
911 def opt_version(self
):
913 print "Buildbot version: %s" % buildbot
.version
914 usage
.Options
.opt_version(self
)
916 def opt_verbose(self
):
917 from twisted
.python
import log
918 log
.startLogging(sys
.stderr
)
920 def postOptions(self
):
921 if not hasattr(self
, 'subOptions'):
922 raise usage
.UsageError("must specify a command")
928 config
.parseOptions()
929 except usage
.error
, e
:
930 print "%s: %s" % (sys
.argv
[0], e
)
932 c
= getattr(config
, 'subOptions', config
)
936 command
= config
.subCommand
937 so
= config
.subOptions
939 if command
== "create-master":
941 elif command
== "upgrade-master":
943 elif command
== "create-slave":
945 elif command
== "start":
946 from buildbot
.scripts
.startup
import start
948 elif command
== "stop":
950 elif command
== "restart":
952 elif command
== "reconfig" or command
== "sighup":
953 from buildbot
.scripts
.reconfig
import Reconfigurator
954 Reconfigurator().run(so
)
955 elif command
== "sendchange":
957 elif command
== "debugclient":
959 elif command
== "statuslog":
961 elif command
== "statusgui":
963 elif command
== "try":
965 elif command
== "tryserver":
967 elif command
== "checkconfig":