1
2
3
4
5 import os, sys, stat, re, time
6 import traceback
7 from twisted.python import usage, util, runtime
8
9 from buildbot.interfaces import BuildbotNotRunningError
10
11
12
13
14
15
16
17
19 optFlags = [
20 ['help', 'h', "Display this message"],
21 ["quiet", "q", "Do not emit the commands being run"],
22 ]
23
24
25 opt_h = usage.Options.opt_help
26
28 if len(args) > 0:
29 self['basedir'] = args[0]
30 else:
31 self['basedir'] = None
32 if len(args) > 1:
33 raise usage.UsageError("I wasn't expecting so many arguments")
34
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'])
39
40 makefile_sample = """# -*- makefile -*-
41
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'.
45
46 # The 'reconfig' target will tell a buildmaster to reload its config file.
47
48 start:
49 twistd --no_save -y buildbot.tac
50
51 stop:
52 kill `cat twistd.pid`
53
54 reconfig:
55 kill -HUP `cat twistd.pid`
56
57 log:
58 tail -f twistd.log
59 """
60
63 self.config = config
64 self.basedir = config['basedir']
65 self.force = config.get('force', False)
66 self.quiet = config['quiet']
67
69 if os.path.exists(self.basedir):
70 if not self.quiet:
71 print "updating existing installation"
72 return
73 if not self.quiet: print "mkdir", self.basedir
74 os.mkdir(self.basedir)
75
77 path = os.path.join(self.basedir, "info")
78 if not os.path.exists(path):
79 if not self.quiet: print "mkdir", path
80 os.mkdir(path)
81 created = False
82 admin = os.path.join(path, "admin")
83 if not os.path.exists(admin):
84 if not self.quiet:
85 print "Creating info/admin, you need to edit it appropriately"
86 f = open(admin, "wt")
87 f.write("Your Name Here <admin@youraddress.invalid>\n")
88 f.close()
89 created = True
90 host = os.path.join(path, "host")
91 if not os.path.exists(host):
92 if not self.quiet:
93 print "Creating info/host, you need to edit it appropriately"
94 f = open(host, "wt")
95 f.write("Please put a description of this build host here\n")
96 f.close()
97 created = True
98 if created and not self.quiet:
99 print "Please edit the files in %s appropriately." % path
100
104
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:
110 if not self.quiet:
111 print "buildbot.tac already exists and is correct"
112 return
113 if not self.quiet:
114 print "not touching existing buildbot.tac"
115 print "creating buildbot.tac.new instead"
116 tacfile = "buildbot.tac.new"
117 f = open(tacfile, "wt")
118 f.write(contents)
119 f.close()
120 if secret:
121 os.chmod(tacfile, 0600)
122
124 target = "Makefile.sample"
125 if os.path.exists(target):
126 oldcontents = open(target, "rt").read()
127 if oldcontents == makefile_sample:
128 if not self.quiet:
129 print "Makefile.sample already exists and is correct"
130 return
131 if not self.quiet:
132 print "replacing Makefile.sample"
133 else:
134 if not self.quiet:
135 print "creating Makefile.sample"
136 f = open(target, "wt")
137 f.write(makefile_sample)
138 f.close()
139
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:
146 if not self.quiet:
147 print "master.cfg.sample already exists and is up-to-date"
148 return
149 if not self.quiet:
150 print "replacing master.cfg.sample"
151 else:
152 if not self.quiet:
153 print "creating master.cfg.sample"
154 f = open(target, "wt")
155 f.write(config_sample)
156 f.close()
157 os.chmod(target, 0600)
158
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):
162 if not self.quiet:
163 print "public_html/ already exists: not replacing"
164 return
165 else:
166 os.mkdir(webdir)
167 if not self.quiet:
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())
172 f.close()
173
174 target = os.path.join(webdir, "buildbot.css")
175 f = open(target, "wt")
176 f.write(open(buildbot_css, "rt").read())
177 f.close()
178
179 target = os.path.join(webdir, "robots.txt")
180 f = open(target, "wt")
181 f.write(open(robots_txt, "rt").read())
182 f.close()
183
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:
189 if overwrite:
190 if not self.quiet:
191 print "%s has old/modified contents" % target
192 print " overwriting it with new contents"
193 open(target, "wt").write(new_contents)
194 else:
195 if not self.quiet:
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
200 else:
201 if not self.quiet:
202 print "populating %s" % target
203 open(target, "wt").write(new_contents)
204
217
219 from buildbot.master import BuildMaster
220 from twisted.python import log, failure
221
222 master_cfg = os.path.join(self.basedir, "master.cfg")
223 if not os.path.exists(master_cfg):
224 if not self.quiet:
225 print "No master.cfg found"
226 return 1
227
228
229
230
231
232
233
234
235
236
237
238
239
240 if sys.path[0] != self.basedir:
241 sys.path.insert(0, self.basedir)
242
243 m = BuildMaster(self.basedir)
244
245
246
247
248 messages = []
249 log.addObserver(messages.append)
250 try:
251
252
253
254
255 m.loadConfig(open(master_cfg, "r"))
256 except:
257 f = failure.Failure()
258 if not self.quiet:
259 print
260 for m in messages:
261 print "".join(m['message'])
262 print f
263 print
264 print "An error was detected in the master.cfg file."
265 print "Please correct the problem and run 'buildbot upgrade-master' again."
266 print
267 return 1
268 return 0
269
271 optFlags = [
272 ["replace", "r", "Replace any modified files without confirmation."],
273 ]
274
276 return "Usage: buildbot upgrade-master [options] <basedir>"
277
278 longdesc = """
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.
283
284 Use this after you've upgraded your buildbot installation and before you
285 restart the buildmaster to use the new version.
286
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.
292 """
293
295 basedir = config['basedir']
296 m = Maker(config)
297
298
299
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"),
304 )
305 m.populate_if_missing(os.path.join(basedir, "master.cfg.sample"),
306 util.sibpath(__file__, "sample.cfg"),
307 overwrite=True)
308 rc = m.check_master_cfg()
309 if rc:
310 return rc
311 if not config['quiet']:
312 print "upgrade complete"
313
314
316 optFlags = [
317 ["force", "f",
318 "Re-use an existing directory (will not overwrite master.cfg file)"],
319 ]
320 optParameters = [
321 ["config", "c", "master.cfg", "name of the buildmaster config file"],
322 ["log-size", "s", "1000000",
323 "size at which to rotate twisted log files"],
324 ["log-count", "l", "None",
325 "limit the number of kept old twisted log files"],
326 ]
328 return "Usage: buildbot create-master [options] <basedir>"
329
330 longdesc = """
331 This command creates a buildmaster working directory and buildbot.tac
332 file. The master will live in <dir> and create various files there.
333
334 At runtime, the master will read a configuration file (named
335 'master.cfg' by default) in its basedir. This file should contain python
336 code which eventually defines a dictionary named 'BuildmasterConfig'.
337 The elements of this dictionary are used to configure the Buildmaster.
338 See doc/config.xhtml for details about what can be controlled through
339 this interface."""
340
341 - def postOptions(self):
342 MakerBase.postOptions(self)
343 if not re.match('^\d+$', self['log-size']):
344 raise usage.UsageError("log-size parameter needs to be an int")
345 if not re.match('^\d+$', self['log-count']) and \
346 self['log-count'] != 'None':
347 raise usage.UsageError("log-count parameter needs to be an int "+
348 " or None")
349
350
351 masterTAC = """
352 from twisted.application import service
353 from buildbot.master import BuildMaster
354
355 basedir = r'%(basedir)s'
356 configfile = r'%(config)s'
357 rotateLength = %(log-size)s
358 maxRotatedFiles = %(log-count)s
359
360 application = service.Application('buildmaster')
361 try:
362 from twisted.python.logfile import LogFile
363 from twisted.python.log import ILogObserver, FileLogObserver
364 logfile = LogFile.fromFullPath("twistd.log", rotateLength=rotateLength,
365 maxRotatedFiles=maxRotatedFiles)
366 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
367 except ImportError:
368 # probably not yet twisted 8.2.0 and beyond, can't set log yet
369 pass
370 BuildMaster(basedir, configfile).setServiceParent(application)
371
372 """
373
388
390 optFlags = [
391 ["force", "f", "Re-use an existing directory"],
392 ]
393 optParameters = [
394
395
396
397
398
399
400 ["keepalive", "k", 600,
401 "Interval at which keepalives should be sent (in seconds)"],
402 ["usepty", None, 0,
403 "(1 or 0) child processes should be run in a pty (default 0)"],
404 ["umask", None, "None",
405 "controls permissions of generated files. Use --umask=022 to be world-readable"],
406 ["maxdelay", None, 300,
407 "Maximum time between connection attempts"],
408 ["log-size", "s", "1000000",
409 "size at which to rotate twisted log files"],
410 ["log-count", "l", "None",
411 "limit the number of kept old twisted log files"],
412 ]
413
414 longdesc = """
415 This command creates a buildslave working directory and buildbot.tac
416 file. The bot will use the <name> and <passwd> arguments to authenticate
417 itself when connecting to the master. All commands are run in a
418 build-specific subdirectory of <basedir>. <master> is a string of the
419 form 'hostname:port', and specifies where the buildmaster can be reached.
420
421 <name>, <passwd>, and <master> will be provided by the buildmaster
422 administrator for your bot. You must choose <basedir> yourself.
423 """
424
426 return "Usage: buildbot create-slave [options] <basedir> <master> <name> <passwd>"
427
429 if len(args) < 4:
430 raise usage.UsageError("command needs more arguments")
431 basedir, master, name, passwd = args
432 if master[:5] == "http:":
433 raise usage.UsageError("<master> is not a URL - do not use URL")
434 self['basedir'] = basedir
435 self['master'] = master
436 self['name'] = name
437 self['passwd'] = passwd
438
439 - def postOptions(self):
440 MakerBase.postOptions(self)
441 self['usepty'] = int(self['usepty'])
442 self['keepalive'] = int(self['keepalive'])
443 self['maxdelay'] = int(self['maxdelay'])
444 if self['master'].find(":") == -1:
445 raise usage.UsageError("--master must be in the form host:portnum")
446 if not re.match('^\d+$', self['log-size']):
447 raise usage.UsageError("log-size parameter needs to be an int")
448 if not re.match('^\d+$', self['log-count']) and \
449 self['log-count'] != 'None':
450 raise usage.UsageError("log-count parameter needs to be an int "+
451 " or None")
452
453 slaveTAC = """
454 from twisted.application import service
455 from buildbot.slave.bot import BuildSlave
456
457 basedir = r'%(basedir)s'
458 buildmaster_host = '%(host)s'
459 port = %(port)d
460 slavename = '%(name)s'
461 passwd = '%(passwd)s'
462 keepalive = %(keepalive)d
463 usepty = %(usepty)d
464 umask = %(umask)s
465 maxdelay = %(maxdelay)d
466 rotateLength = %(log-size)s
467 maxRotatedFiles = %(log-count)s
468
469 application = service.Application('buildslave')
470 try:
471 from twisted.python.logfile import LogFile
472 from twisted.python.log import ILogObserver, FileLogObserver
473 logfile = LogFile.fromFullPath("twistd.log", rotateLength=rotateLength,
474 maxRotatedFiles=maxRotatedFiles)
475 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
476 except ImportError:
477 # probably not yet twisted 8.2.0 and beyond, can't set log yet
478 pass
479 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir,
480 keepalive, usepty, umask=umask, maxdelay=maxdelay)
481 s.setServiceParent(application)
482
483 """
484
486 m = Maker(config)
487 m.mkdir()
488 m.chdir()
489 try:
490 master = config['master']
491 host, port = re.search(r'(.+):(\d+)', master).groups()
492 config['host'] = host
493 config['port'] = int(port)
494 except:
495 print "unparseable master location '%s'" % master
496 print " expecting something more like localhost:8007"
497 raise
498 contents = slaveTAC % config
499
500 m.makeTAC(contents, secret=True)
501
502 m.makefile()
503 m.mkinfo()
504
505 if not m.quiet: print "buildslave configured in %s" % m.basedir
506
507
508
509 -def stop(config, signame="TERM", wait=False):
510 import signal
511 basedir = config['basedir']
512 quiet = config['quiet']
513 os.chdir(basedir)
514 try:
515 f = open("twistd.pid", "rt")
516 except:
517 raise BuildbotNotRunningError
518 pid = int(f.read().strip())
519 signum = getattr(signal, "SIG"+signame)
520 timer = 0
521 os.kill(pid, signum)
522 if not wait:
523 if not quiet:
524 print "sent SIG%s to process" % signame
525 return
526 time.sleep(0.1)
527 while timer < 10:
528
529 try:
530 os.kill(pid, 0)
531 except OSError:
532 if not quiet:
533 print "buildbot process %d is dead" % pid
534 return
535 timer += 1
536 time.sleep(1)
537 if not quiet:
538 print "never saw process go away"
539
550
551
552 -def loadOptions(filename="options", here=None, home=None):
553 """Find the .buildbot/FILENAME file. Crawl from the current directory up
554 towards the root, and also look in ~/.buildbot . The first directory
555 that's owned by the user and has the file we're looking for wins. Windows
556 skips the owned-by-user test.
557
558 @rtype: dict
559 @return: a dictionary of names defined in the options file. If no options
560 file was found, return an empty dict.
561 """
562
563 if here is None:
564 here = os.getcwd()
565 here = os.path.abspath(here)
566
567 if home is None:
568 if runtime.platformType == 'win32':
569 home = os.path.join(os.environ['APPDATA'], "buildbot")
570 else:
571 home = os.path.expanduser("~/.buildbot")
572
573 searchpath = []
574 toomany = 20
575 while True:
576 searchpath.append(os.path.join(here, ".buildbot"))
577 next = os.path.dirname(here)
578 if next == here:
579 break
580 here = next
581 toomany -= 1
582 if toomany == 0:
583 raise ValueError("Hey, I seem to have wandered up into the "
584 "infinite glories of the heavens. Oops.")
585 searchpath.append(home)
586
587 localDict = {}
588
589 for d in searchpath:
590 if os.path.isdir(d):
591 if runtime.platformType != 'win32':
592 if os.stat(d)[stat.ST_UID] != os.getuid():
593 print "skipping %s because you don't own it" % d
594 continue
595 optfile = os.path.join(d, filename)
596 if os.path.exists(optfile):
597 try:
598 f = open(optfile, "r")
599 options = f.read()
600 exec options in localDict
601 except:
602 print "error while reading %s" % optfile
603 raise
604 break
605
606 for k in localDict.keys():
607 if k.startswith("__"):
608 del localDict[k]
609 return localDict
610
612 optFlags = [
613 ['quiet', 'q', "Don't display startup log messages"],
614 ]
616 return "Usage: buildbot start <basedir>"
617
620 return "Usage: buildbot stop <basedir>"
621
623 optFlags = [
624 ['quiet', 'q', "Don't display log messages about reconfiguration"],
625 ]
627 return "Usage: buildbot reconfig <basedir>"
628
629
630
632 optFlags = [
633 ['quiet', 'q', "Don't display startup log messages"],
634 ]
636 return "Usage: buildbot restart <basedir>"
637
639 optFlags = [
640 ['help', 'h', "Display this message"],
641 ]
642 optParameters = [
643 ["master", "m", None,
644 "Location of the buildmaster's slaveport (host:port)"],
645 ["passwd", "p", None, "Debug password to use"],
646 ]
647
649 if len(args) > 0:
650 self['master'] = args[0]
651 if len(args) > 1:
652 self['passwd'] = args[1]
653 if len(args) > 2:
654 raise usage.UsageError("I wasn't expecting so many arguments")
655
657 from buildbot.clients import debug
658 opts = loadOptions()
659
660 master = config.get('master')
661 if not master:
662 master = opts.get('master')
663 if master is None:
664 raise usage.UsageError("master must be specified: on the command "
665 "line or in ~/.buildbot/options")
666
667 passwd = config.get('passwd')
668 if not passwd:
669 passwd = opts.get('debugPassword')
670 if passwd is None:
671 raise usage.UsageError("passwd must be specified: on the command "
672 "line or in ~/.buildbot/options")
673
674 d = debug.DebugWidget(master, passwd)
675 d.run()
676
678 optFlags = [
679 ['help', 'h', "Display this message"],
680 ]
681 optParameters = [
682 ["master", "m", None,
683 "Location of the buildmaster's status port (host:port)"],
684 ]
685
687 if len(args) > 0:
688 self['master'] = args[0]
689 if len(args) > 1:
690 raise usage.UsageError("I wasn't expecting so many arguments")
691
703
715
717 optParameters = [
718 ("master", "m", None,
719 "Location of the buildmaster's PBListener (host:port)"),
720 ("username", "u", None, "Username performing the commit"),
721 ("branch", "b", None, "Branch specifier"),
722 ("category", "c", None, "Category of repository"),
723 ("revision", "r", None, "Revision specifier (string)"),
724 ("revision_number", "n", None, "Revision specifier (integer)"),
725 ("revision_file", None, None, "Filename containing revision spec"),
726 ("comments", "m", None, "log message"),
727 ("logfile", "F", None,
728 "Read the log messages from this file (- for stdin)"),
729 ("when", "w", None, "timestamp to use as the change time"),
730 ]
732 return "Usage: buildbot sendchange [options] filenames.."
735
736
738 """Send a single change to the buildmaster's PBChangeSource. The
739 connection will be drpoped as soon as the Change has been sent."""
740 from buildbot.clients.sendchange import Sender
741
742 opts = loadOptions()
743 user = config.get('username', opts.get('username'))
744 master = config.get('master', opts.get('master'))
745 branch = config.get('branch', opts.get('branch'))
746 category = config.get('category', opts.get('category'))
747 revision = config.get('revision')
748 if config.get('when'):
749 when = float(config.get('when'))
750 else:
751 when = None
752
753 if config.get("revision_number"):
754 revision = int(config['revision_number'])
755 if config.get("revision_file"):
756 revision = open(config["revision_file"],"r").read()
757
758 comments = config.get('comments')
759 if not comments and config.get('logfile'):
760 if config['logfile'] == "-":
761 f = sys.stdin
762 else:
763 f = open(config['logfile'], "rt")
764 comments = f.read()
765 if comments is None:
766 comments = ""
767
768 files = config.get('files', [])
769
770 assert user, "you must provide a username"
771 assert master, "you must provide the master location"
772
773 s = Sender(master, user)
774 d = s.send(branch, revision, comments, files, category=category, when=when)
775 if runReactor:
776 d.addCallbacks(s.printSuccess, s.printFailure)
777 d.addBoth(s.stop)
778 s.run()
779 return d
780
781
783 optParameters = [
784 ["builder", None, None, "which Builder to start"],
785 ["branch", None, None, "which branch to build"],
786 ["revision", None, None, "which revision to build"],
787 ["reason", None, None, "the reason for starting the build"],
788 ]
789
791 args = list(args)
792 if len(args) > 0:
793 if self['builder'] is not None:
794 raise usage.UsageError("--builder provided in two ways")
795 self['builder'] = args.pop(0)
796 if len(args) > 0:
797 if self['reason'] is not None:
798 raise usage.UsageError("--reason provided in two ways")
799 self['reason'] = " ".join(args)
800
801
803 optParameters = [
804 ["connect", "c", None,
805 "how to reach the buildmaster, either 'ssh' or 'pb'"],
806
807 ["tryhost", None, None,
808 "the hostname (used by ssh) for the buildmaster"],
809 ["trydir", None, None,
810 "the directory (on the tryhost) where tryjobs are deposited"],
811 ["username", "u", None, "Username performing the trial build"],
812
813 ["master", "m", None,
814 "Location of the buildmaster's PBListener (host:port)"],
815 ["passwd", None, None, "password for PB authentication"],
816
817 ["diff", None, None,
818 "Filename of a patch to use instead of scanning a local tree. Use '-' for stdin."],
819 ["patchlevel", "p", 0,
820 "Number of slashes to remove from patch pathnames, like the -p option to 'patch'"],
821
822 ["baserev", None, None,
823 "Base revision to use instead of scanning a local tree."],
824
825 ["vc", None, None,
826 "The VC system in use, one of: cvs,svn,tla,baz,darcs"],
827 ["branch", None, None,
828 "The branch in use, for VC systems that can't figure it out"
829 " themselves"],
830
831 ["builder", "b", None,
832 "Run the trial build on this Builder. Can be used multiple times."],
833 ["properties", None, None,
834 "A set of properties made available in the build environment, format:prop=value,propb=valueb..."],
835 ]
836
837 optFlags = [
838 ["wait", None, "wait until the builds have finished"],
839 ["dryrun", 'n', "Gather info, but don't actually submit."],
840 ]
841
843 super(TryOptions, self).__init__()
844 self['builders'] = []
845 self['properties'] = {}
846
848 self['builders'].append(option)
849
851
852 properties = {}
853 propertylist = option.split(",")
854 for i in range(0,len(propertylist)):
855 print propertylist[i]
856 splitproperty = propertylist[i].split("=")
857 properties[splitproperty[0]] = splitproperty[1]
858 self['properties'] = properties
859
861 self['patchlevel'] = int(option)
862
864 return "Usage: buildbot try [options]"
865
870
872 optParameters = [
873 ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"],
874 ]
875
877 import md5
878 jobdir = os.path.expanduser(config["jobdir"])
879 job = sys.stdin.read()
880
881
882
883 timestring = "%d" % time.time()
884 jobhash = md5.new(job).hexdigest()
885 fn = "%s-%s" % (timestring, jobhash)
886 tmpfile = os.path.join(jobdir, "tmp", fn)
887 newfile = os.path.join(jobdir, "new", fn)
888 f = open(tmpfile, "w")
889 f.write(job)
890 f.close()
891 os.rename(tmpfile, newfile)
892
893
895 optFlags = [
896 ['quiet', 'q', "Don't display error messages or tracebacks"],
897 ]
898
900 return "Usage :buildbot checkconfig [configFile]\n" + \
901 " If not specified, 'master.cfg' will be used as 'configFile'"
902
904 if len(args) >= 1:
905 self['configFile'] = args[0]
906 else:
907 self['configFile'] = 'master.cfg'
908
909
911 quiet = config.get('quiet')
912 configFileName = config.get('configFile')
913 try:
914 from buildbot.scripts.checkconfig import ConfigLoader
915 if os.path.isdir(configFileName):
916 ConfigLoader(basedir=configFileName)
917 else:
918 ConfigLoader(configFileName=configFileName)
919 except:
920 if not quiet:
921
922 t, v, tb = sys.exc_info()
923 traceback.print_exception(t, v, tb)
924 sys.exit(1)
925
926 if not quiet:
927 print "Config file is good!"
928
929
931 synopsis = "Usage: buildbot <command> [command options]"
932
933 subCommands = [
934
935 ['create-master', None, MasterOptions,
936 "Create and populate a directory for a new buildmaster"],
937 ['upgrade-master', None, UpgradeMasterOptions,
938 "Upgrade an existing buildmaster directory for the current version"],
939 ['create-slave', None, SlaveOptions,
940 "Create and populate a directory for a new buildslave"],
941 ['start', None, StartOptions, "Start a buildmaster or buildslave"],
942 ['stop', None, StopOptions, "Stop a buildmaster or buildslave"],
943 ['restart', None, RestartOptions,
944 "Restart a buildmaster or buildslave"],
945
946 ['reconfig', None, ReconfigOptions,
947 "SIGHUP a buildmaster to make it re-read the config file"],
948 ['sighup', None, ReconfigOptions,
949 "SIGHUP a buildmaster to make it re-read the config file"],
950
951 ['sendchange', None, SendChangeOptions,
952 "Send a change to the buildmaster"],
953
954 ['debugclient', None, DebugClientOptions,
955 "Launch a small debug panel GUI"],
956
957 ['statuslog', None, StatusClientOptions,
958 "Emit current builder status to stdout"],
959 ['statusgui', None, StatusClientOptions,
960 "Display a small window showing current builder status"],
961
962
963 ['try', None, TryOptions, "Run a build with your local changes"],
964
965 ['tryserver', None, TryServerOptions,
966 "buildmaster-side 'try' support function, not for users"],
967
968 ['checkconfig', None, CheckConfigOptions,
969 "test the validity of a master.cfg config file"],
970
971
972 ]
973
978
980 from twisted.python import log
981 log.startLogging(sys.stderr)
982
983 - def postOptions(self):
984 if not hasattr(self, 'subOptions'):
985 raise usage.UsageError("must specify a command")
986
987
1032