1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import copy
24 import os, sys, stat, re, time
25 from twisted.python import usage, util, runtime
26 from twisted.internet import defer
27
28 from buildbot.interfaces import BuildbotNotRunningError
31 """decorate a function by running it with maybeDeferred in a reactor"""
32 def wrap(*args, **kwargs):
33 from twisted.internet import reactor
34 result = [ ]
35 def async():
36 d = defer.maybeDeferred(f, *args, **kwargs)
37 def eb(f):
38 f.printTraceback()
39 d.addErrback(eb)
40 def do_stop(r):
41 result.append(r)
42 reactor.stop()
43 d.addBoth(do_stop)
44 reactor.callWhenRunning(async)
45 reactor.run()
46 return result[0]
47 wrap.__doc__ = f.__doc__
48 wrap.__name__ = f.__name__
49 wrap._orig = f
50 return wrap
51
53 buildbot_tac = os.path.join(dir, "buildbot.tac")
54 if not os.path.isfile(buildbot_tac):
55 print "no buildbot.tac"
56 return False
57
58 contents = open(buildbot_tac, "r").read()
59 return "Application('buildmaster')" in contents
60
95
97 """Find the .buildbot/FILENAME file. Crawl from the current directory up
98 towards the root, and also look in ~/.buildbot . The first directory
99 that's owned by the user and has the file we're looking for wins. Windows
100 skips the owned-by-user test.
101
102 @rtype: dict
103 @return: a dictionary of names defined in the options file. If no options
104 file was found, return an empty dict.
105 """
106
107 here = os.path.abspath(os.getcwd())
108
109 if runtime.platformType == 'win32':
110
111 from win32com.shell import shellcon, shell
112 appdata = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)
113 home = os.path.join(appdata, "buildbot")
114 else:
115 home = os.path.expanduser("~/.buildbot")
116
117 searchpath = []
118 toomany = 20
119 while True:
120 searchpath.append(os.path.join(here, ".buildbot"))
121 next = os.path.dirname(here)
122 if next == here:
123 break
124 here = next
125 toomany -= 1
126 if toomany == 0:
127 raise ValueError("Hey, I seem to have wandered up into the "
128 "infinite glories of the heavens. Oops.")
129 searchpath.append(home)
130
131 localDict = {}
132
133 for d in searchpath:
134 if os.path.isdir(d):
135 if runtime.platformType != 'win32':
136 if os.stat(d)[stat.ST_UID] != os.getuid():
137 print "skipping %s because you don't own it" % d
138 continue
139 optfile = os.path.join(d, "options")
140 if os.path.exists(optfile):
141 try:
142 f = open(optfile, "r")
143 options = f.read()
144 exec options in localDict
145 except:
146 print "error while reading %s" % optfile
147 raise
148 break
149
150 for k in localDict.keys():
151 if k.startswith("__"):
152 del localDict[k]
153 return localDict
154
156 optFlags = [
157 ['help', 'h', "Display this message"],
158 ["quiet", "q", "Do not emit the commands being run"],
159 ]
160
161 usage.Options.longdesc = """
162 Operates upon the specified <basedir> (or the current directory, if not
163 specified).
164 """
165
166 opt_h = usage.Options.opt_help
167
169 if len(args) > 0:
170 self['basedir'] = args[0]
171 else:
172
173 self['basedir'] = os.getcwd()
174 if len(args) > 1:
175 raise usage.UsageError("I wasn't expecting so many arguments")
176
177 - def postOptions(self):
178 self['basedir'] = os.path.abspath(self['basedir'])
179
180 makefile_sample = """# -*- makefile -*-
181
182 # This is a simple makefile which lives in a buildmaster
183 # directory (next to the buildbot.tac file). It allows you to start/stop the
184 # master by doing 'make start' or 'make stop'.
185
186 # The 'reconfig' target will tell a buildmaster to reload its config file.
187
188 start:
189 twistd --no_save -y buildbot.tac
190
191 stop:
192 if [ -e twistd.pid ]; \\
193 then kill `cat twistd.pid`; \\
194 else echo "Nothing to stop."; \\
195 fi
196
197 reconfig:
198 if [ -e twistd.pid ]; \\
199 then kill -HUP `cat twistd.pid`; \\
200 else echo "Nothing to reconfig."; \\
201 fi
202
203 log:
204 if [ -e twistd.log ]; \\
205 then tail -f twistd.log; \\
206 else echo "Nothing to tail."; \\
207 fi
208 """
216
224
228
229 - def makeTAC(self, contents, secret=False):
230 tacfile = "buildbot.tac"
231 if os.path.exists(tacfile):
232 oldcontents = open(tacfile, "rt").read()
233 if oldcontents == contents:
234 if not self.quiet:
235 print "buildbot.tac already exists and is correct"
236 return
237 if not self.quiet:
238 print "not touching existing buildbot.tac"
239 print "creating buildbot.tac.new instead"
240 tacfile = "buildbot.tac.new"
241 f = open(tacfile, "wt")
242 f.write(contents)
243 f.close()
244 if secret:
245 os.chmod(tacfile, 0600)
246
248 target = "Makefile.sample"
249 if os.path.exists(target):
250 oldcontents = open(target, "rt").read()
251 if oldcontents == makefile_sample:
252 if not self.quiet:
253 print "Makefile.sample already exists and is correct"
254 return
255 if not self.quiet:
256 print "replacing Makefile.sample"
257 else:
258 if not self.quiet:
259 print "creating Makefile.sample"
260 f = open(target, "wt")
261 f.write(makefile_sample)
262 f.close()
263
265 target = "master.cfg.sample"
266 config_sample = open(source, "rt").read()
267 if os.path.exists(target):
268 oldcontents = open(target, "rt").read()
269 if oldcontents == config_sample:
270 if not self.quiet:
271 print "master.cfg.sample already exists and is up-to-date"
272 return
273 if not self.quiet:
274 print "replacing master.cfg.sample"
275 else:
276 if not self.quiet:
277 print "creating master.cfg.sample"
278 f = open(target, "wt")
279 f.write(config_sample)
280 f.close()
281 os.chmod(target, 0600)
282
284 webdir = os.path.join(self.basedir, "public_html")
285 if os.path.exists(webdir):
286 if not self.quiet:
287 print "public_html/ already exists: not replacing"
288 return
289 else:
290 os.mkdir(webdir)
291 if not self.quiet:
292 print "populating public_html/"
293 for target, source in files.iteritems():
294 target = os.path.join(webdir, target)
295 f = open(target, "wt")
296 f.write(open(source, "rt").read())
297 f.close()
298
307
309 new_contents = open(source, "rt").read()
310 if os.path.exists(target):
311 old_contents = open(target, "rt").read()
312 if old_contents != new_contents:
313 if overwrite:
314 if not self.quiet:
315 print "%s has old/modified contents" % target
316 print " overwriting it with new contents"
317 open(target, "wt").write(new_contents)
318 else:
319 if not self.quiet:
320 print "%s has old/modified contents" % target
321 print " writing new contents to %s.new" % target
322 open(target + ".new", "wt").write(new_contents)
323
324 else:
325 if not self.quiet:
326 print "populating %s" % target
327 open(target, "wt").write(new_contents)
328
330 if os.path.exists(source):
331 if os.path.exists(dest):
332 print "Notice: %s now overrides %s" % (dest, source)
333 print " as the latter is not used by buildbot anymore."
334 print " Decide which one you want to keep."
335 else:
336 try:
337 print "Notice: Moving %s to %s." % (source, dest)
338 print " You can (and probably want to) remove it if you haven't modified this file."
339 os.renames(source, dest)
340 except Exception, e:
341 print "Error moving %s to %s: %s" % (source, dest, str(e))
342
352
354 """Check the buildmaster configuration, returning a deferred that
355 fires with an approprate exit status (so 0=success)."""
356 from buildbot.master import BuildMaster
357 from twisted.python import log
358
359 master_cfg = os.path.join(self.basedir, "master.cfg")
360 if not os.path.exists(master_cfg):
361 if not self.quiet:
362 print "No master.cfg found"
363 return defer.succeed(1)
364
365
366
367
368
369
370
371
372
373
374
375
376
377 if sys.path[0] != self.basedir:
378 sys.path.insert(0, self.basedir)
379
380 m = BuildMaster(self.basedir)
381
382
383
384
385
386 messages = []
387 log.addObserver(messages.append)
388
389
390
391
392 d = defer.maybeDeferred(lambda :
393 m.loadConfig(open(master_cfg, "r"), checkOnly=True))
394 def check_db_url(config):
395 if (expected_db_url and
396 config.get('db_url', 'sqlite:///state.sqlite') != expected_db_url):
397 raise ValueError("c['db_url'] in the config file ('%s') does"
398 " not match '%s'; please edit the configuration"
399 " file before upgrading." %
400 (config['db_url'], expected_db_url))
401 d.addCallback(check_db_url)
402 def cb(_):
403 return 0
404 def eb(f):
405 if not self.quiet:
406 print
407 for m in messages:
408 print "".join(m['message'])
409 f.printTraceback()
410 print
411 print "An error was detected in the master.cfg file."
412 print "Please correct the problem and run 'buildbot upgrade-master' again."
413 print
414 return 1
415 d.addCallbacks(cb, eb)
416 return d
417
418 DB_HELP = """
419 The --db string is evaluated to build the DB object, which specifies
420 which database the buildmaster should use to hold scheduler state and
421 status information. The default (which creates an SQLite database in
422 BASEDIR/state.sqlite) is equivalent to:
423
424 --db='sqlite:///state.sqlite'
425
426 To use a remote MySQL database instead, use something like:
427
428 --db='mysql://bbuser:bbpasswd@dbhost/bbdb'
429 """
432 optFlags = [
433 ["replace", "r", "Replace any modified files without confirmation."],
434 ]
435 optParameters = [
436 ["db", None, "sqlite:///state.sqlite",
437 "which DB to use for scheduler/status state. See below for syntax."],
438 ]
439
441 return "Usage: buildbot upgrade-master [options] [<basedir>]"
442
443 longdesc = """
444 This command takes an existing buildmaster working directory and
445 adds/modifies the files there to work with the current version of
446 buildbot. When this command is finished, the buildmaster directory should
447 look much like a brand-new one created by the 'create-master' command.
448
449 Use this after you've upgraded your buildbot installation and before you
450 restart the buildmaster to use the new version.
451
452 If you have modified the files in your working directory, this command
453 will leave them untouched, but will put the new recommended contents in a
454 .new file (for example, if index.html has been modified, this command
455 will create index.html.new). You can then look at the new version and
456 decide how to merge its contents into your modified file.
457 """+DB_HELP+"""
458 When upgrading from a pre-0.8.0 release (which did not use a database),
459 this command will create the given database and migrate data from the old
460 pickle files into it, then move the pickle files out of the way (e.g. to
461 changes.pck.old). To revert to an older release, rename the pickle files
462 back. When you are satisfied with the new version, you can delete the old
463 pickle files.
464 """
465
466 @in_reactor
467 @defer.deferredGenerator
468 -def upgradeMaster(config):
469 m = Maker(config)
470
471 if not config['quiet']: print "upgrading basedir"
472 basedir = os.path.expanduser(config['basedir'])
473
474
475
476 m.upgrade_public_html({
477 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"),
478 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"),
479 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"),
480 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"),
481 })
482 m.populate_if_missing(os.path.join(basedir, "master.cfg.sample"),
483 util.sibpath(__file__, "sample.cfg"),
484 overwrite=True)
485
486 m.move_if_present(os.path.join(basedir, "public_html/index.html"),
487 os.path.join(basedir, "templates/root.html"))
488
489 if not config['quiet']: print "checking master.cfg"
490 wfd = defer.waitForDeferred(
491 m.check_master_cfg(expected_db_url=config['db']))
492 yield wfd
493 rc = wfd.getResult()
494
495 if rc == 0:
496 from buildbot.db import connector
497 from buildbot.master import BuildMaster
498
499 if not config['quiet']: print "upgrading database"
500 db = connector.DBConnector(BuildMaster(config['basedir']),
501 config['db'],
502 basedir=config['basedir'])
503
504 wfd = defer.waitForDeferred(
505 db.model.upgrade())
506 yield wfd
507 wfd.getResult()
508
509 if not config['quiet']: print "upgrade complete"
510 yield 0
511 else:
512 yield rc
513
516 optFlags = [
517 ["force", "f",
518 "Re-use an existing directory (will not overwrite master.cfg file)"],
519 ["relocatable", "r",
520 "Create a relocatable buildbot.tac"],
521 ["no-logrotate", "n",
522 "Do not permit buildmaster rotate logs by itself"]
523 ]
524 optParameters = [
525 ["config", "c", "master.cfg", "name of the buildmaster config file"],
526 ["log-size", "s", "10000000",
527 "size at which to rotate twisted log files"],
528 ["log-count", "l", "10",
529 "limit the number of kept old twisted log files"],
530 ["db", None, "sqlite:///state.sqlite",
531 "which DB to use for scheduler/status state. See below for syntax."],
532 ]
534 return "Usage: buildbot create-master [options] [<basedir>]"
535
536 longdesc = """
537 This command creates a buildmaster working directory and buildbot.tac file.
538 The master will live in <dir> and create various files there. If
539 --relocatable is given, then the resulting buildbot.tac file will be
540 written such that its containing directory is assumed to be the basedir.
541 This is generally a good idea.
542
543 At runtime, the master will read a configuration file (named
544 'master.cfg' by default) in its basedir. This file should contain python
545 code which eventually defines a dictionary named 'BuildmasterConfig'.
546 The elements of this dictionary are used to configure the Buildmaster.
547 See doc/config.xhtml for details about what can be controlled through
548 this interface.
549 """ + DB_HELP + """
550 The --db string is stored verbatim in the buildbot.tac file, and
551 evaluated as 'buildbot start' time to pass a DBConnector instance into
552 the newly-created BuildMaster object.
553 """
554
555 - def postOptions(self):
556 MakerBase.postOptions(self)
557 if not re.match('^\d+$', self['log-size']):
558 raise usage.UsageError("log-size parameter needs to be an int")
559 if not re.match('^\d+$', self['log-count']) and \
560 self['log-count'] != 'None':
561 raise usage.UsageError("log-count parameter needs to be an int "+
562 " or None")
563
564
565 masterTACTemplate = ["""
566 import os
567
568 from twisted.application import service
569 from buildbot.master import BuildMaster
570
571 basedir = r'%(basedir)s'
572 rotateLength = %(log-size)s
573 maxRotatedFiles = %(log-count)s
574
575 # if this is a relocatable tac file, get the directory containing the TAC
576 if basedir == '.':
577 import os.path
578 basedir = os.path.abspath(os.path.dirname(__file__))
579
580 # note: this line is matched against to check that this is a buildmaster
581 # directory; do not edit it.
582 application = service.Application('buildmaster')
583 """,
584 """
585 try:
586 from twisted.python.logfile import LogFile
587 from twisted.python.log import ILogObserver, FileLogObserver
588 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
589 maxRotatedFiles=maxRotatedFiles)
590 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
591 except ImportError:
592 # probably not yet twisted 8.2.0 and beyond, can't set log yet
593 pass
594 """,
595 """
596 configfile = r'%(config)s'
597
598 m = BuildMaster(basedir, configfile)
599 m.setServiceParent(application)
600 m.log_rotation.rotateLength = rotateLength
601 m.log_rotation.maxRotatedFiles = maxRotatedFiles
602
603 """]
631 d.addCallback(print_status)
632 return d
633
634 -def stop(config, signame="TERM", wait=False):
635 import signal
636 basedir = config['basedir']
637 quiet = config['quiet']
638
639 if not isBuildmasterDir(config['basedir']):
640 print "not a buildmaster directory"
641 sys.exit(1)
642
643 os.chdir(basedir)
644 try:
645 f = open("twistd.pid", "rt")
646 except:
647 raise BuildbotNotRunningError
648 pid = int(f.read().strip())
649 signum = getattr(signal, "SIG"+signame)
650 timer = 0
651 try:
652 os.kill(pid, signum)
653 except OSError, e:
654 if e.errno != 3:
655 raise
656
657 if not wait:
658 if not quiet:
659 print "sent SIG%s to process" % signame
660 return
661 time.sleep(0.1)
662 while timer < 10:
663
664 try:
665 os.kill(pid, 0)
666 except OSError:
667 if not quiet:
668 print "buildbot process %d is dead" % pid
669 return
670 timer += 1
671 time.sleep(1)
672 if not quiet:
673 print "never saw process go away"
674
691
694 optFlags = [
695 ['quiet', 'q', "Don't display startup log messages"],
696 ]
698 return "Usage: buildbot start [<basedir>]"
699
702 return "Usage: buildbot stop [<basedir>]"
703
705 optFlags = [
706 ['quiet', 'q', "Don't display log messages about reconfiguration"],
707 ]
709 return "Usage: buildbot reconfig [<basedir>]"
710
714 optFlags = [
715 ['quiet', 'q', "Don't display startup log messages"],
716 ]
718 return "Usage: buildbot restart [<basedir>]"
719
721 optFlags = [
722 ['help', 'h', "Display this message"],
723 ]
724 optParameters = [
725 ["master", "m", None,
726 "Location of the buildmaster's slaveport (host:port)"],
727 ["passwd", "p", None, "Debug password to use"],
728 ]
729 buildbotOptions = [
730 [ 'debugMaster', 'passwd' ],
731 [ 'master', 'master' ],
732 ]
734 return "Usage: buildbot debugclient [options]"
735
737 if len(args) > 0:
738 self['master'] = args[0]
739 if len(args) > 1:
740 self['passwd'] = args[1]
741 if len(args) > 2:
742 raise usage.UsageError("I wasn't expecting so many arguments")
743
759
761 optFlags = [
762 ['help', 'h', "Display this message"],
763 ]
764 optParameters = [
765 ["master", "m", None,
766 "Location of the buildmaster's status port (host:port)"],
767 ["username", "u", "statusClient", "Username performing the trial build"],
768 ["passwd", None, "clientpw", "password for PB authentication"],
769 ]
770 buildbotOptions = [
771 [ 'masterstatus', 'master' ],
772 ]
773
775 if len(args) > 0:
776 self['master'] = args[0]
777 if len(args) > 1:
778 raise usage.UsageError("I wasn't expecting so many arguments")
779
782 return "Usage: buildbot statuslog [options]"
783
786 return "Usage: buildbot statusgui [options]"
787
798
809
814
815 optParameters = [
816 ("master", "m", None,
817 "Location of the buildmaster's PBListener (host:port)"),
818
819 ("username", "u", None, "deprecated name for --who"),
820 ("auth", "a", None, "Authentication token - username:password, or prompt for password"),
821 ("who", "W", None, "Author of the commit"),
822 ("repository", "R", '', "Repository specifier"),
823 ("project", "P", '', "Project specifier"),
824 ("branch", "b", None, "Branch specifier"),
825 ("category", "C", None, "Category of repository"),
826 ("revision", "r", None, "Revision specifier"),
827 ("revision_file", None, None, "Filename containing revision spec"),
828 ("property", "p", None,
829 "A property for the change, in the format: name:value"),
830 ("comments", "c", None, "log message"),
831 ("logfile", "F", None,
832 "Read the log messages from this file (- for stdin)"),
833 ("when", "w", None, "timestamp to use as the change time"),
834 ("revlink", "l", '', "Revision link (revlink)"),
835 ("encoding", "e", 'utf8',
836 "Encoding of other parameters (default utf8)"),
837 ]
838
839 buildbotOptions = [
840 [ 'master', 'master' ],
841 [ 'who', 'who' ],
842
843 [ 'username', 'username' ],
844 [ 'branch', 'branch' ],
845 [ 'category', 'category' ],
846 ]
847
849 return "Usage: buildbot sendchange [options] filenames.."
853 name,value = property.split(':')
854 self['properties'][name] = value
855
858 """Send a single change to the buildmaster's PBChangeSource. The
859 connection will be drpoped as soon as the Change has been sent."""
860 from buildbot.clients import sendchange
861
862 encoding = config.get('encoding', 'utf8')
863 who = config.get('who')
864 if not who and config.get('username'):
865 print "NOTE: --username/-u is deprecated: use --who/-W'"
866 who = config.get('username')
867 auth = config.get('auth')
868 master = config.get('master')
869 branch = config.get('branch')
870 category = config.get('category')
871 revision = config.get('revision')
872 properties = config.get('properties', {})
873 repository = config.get('repository', '')
874 project = config.get('project', '')
875 revlink = config.get('revlink', '')
876 if config.get('when'):
877 when = float(config.get('when'))
878 else:
879 when = None
880 if config.get("revision_file"):
881 revision = open(config["revision_file"],"r").read()
882
883 comments = config.get('comments')
884 if not comments and config.get('logfile'):
885 if config['logfile'] == "-":
886 f = sys.stdin
887 else:
888 f = open(config['logfile'], "rt")
889 comments = f.read()
890 if comments is None:
891 comments = ""
892
893 files = config.get('files', ())
894
895
896 if not auth:
897 auth = 'change:changepw'
898 if ':' not in auth:
899 import getpass
900 pw = getpass.getpass("Enter password for '%s': " % auth)
901 auth = "%s:%s" % (auth, pw)
902 auth = auth.split(':', 1)
903
904 assert who, "you must provide a committer (--who)"
905 assert master, "you must provide the master location"
906
907 s = sendchange.Sender(master, auth, encoding=encoding)
908 d = s.send(branch, revision, comments, files, who=who, category=category, when=when,
909 properties=properties, repository=repository, project=project,
910 revlink=revlink)
911
912 if runReactor:
913 from twisted.internet import reactor
914 status = [True]
915 def printSuccess(_):
916 print "change sent successfully"
917 def failed(f):
918 status[0] = False
919 print "change NOT sent - something went wrong: " + str(f)
920 d.addCallbacks(printSuccess, failed)
921 d.addBoth(lambda _ : reactor.stop())
922 reactor.run()
923 return status[0]
924 return d
925
928 optParameters = [
929 ["builder", None, None, "which Builder to start"],
930 ["branch", None, None, "which branch to build"],
931 ["revision", None, None, "which revision to build"],
932 ["reason", None, None, "the reason for starting the build"],
933 ]
934
936 args = list(args)
937 if len(args) > 0:
938 if self['builder'] is not None:
939 raise usage.UsageError("--builder provided in two ways")
940 self['builder'] = args.pop(0)
941 if len(args) > 0:
942 if self['reason'] is not None:
943 raise usage.UsageError("--reason provided in two ways")
944 self['reason'] = " ".join(args)
945
948 optParameters = [
949 ["connect", "c", None,
950 "How to reach the buildmaster, either 'ssh' or 'pb'"],
951
952 ["host", None, None,
953 "Hostname (used by ssh) for the buildmaster"],
954 ["jobdir", None, None,
955 "Directory (on the buildmaster host) where try jobs are deposited"],
956 ["username", "u", None,
957 "Username performing the try build"],
958
959 ["master", "m", None,
960 "Location of the buildmaster's PBListener (host:port)"],
961 ["passwd", None, None,
962 "Password for PB authentication"],
963 ["who", "w", None,
964 "Who is responsible for the try build"],
965 ["comment", "C", None,
966 "A comment which can be used in notifications for this build"],
967
968 ["diff", None, None,
969 "Filename of a patch to use instead of scanning a local tree. "
970 "Use '-' for stdin."],
971 ["patchlevel", "p", 0,
972 "Number of slashes to remove from patch pathnames, "
973 "like the -p option to 'patch'"],
974
975 ["baserev", None, None,
976 "Base revision to use instead of scanning a local tree."],
977
978 ["vc", None, None,
979 "The VC system in use, one of: bzr, cvs, darcs, git, hg, "
980 "mtn, p4, svn"],
981 ["branch", None, None,
982 "The branch in use, for VC systems that can't figure it out "
983 "themselves"],
984 ["repository", None, None,
985 "Repository to use, instead of path to working directory."],
986
987 ["builder", "b", None,
988 "Run the trial build on this Builder. Can be used multiple times."],
989 ["properties", None, None,
990 "A set of properties made available in the build environment, "
991 "format is --properties=prop1=value1,prop2=value2,.. "
992 "option can be specified multiple times."],
993
994 ["topfile", None, None,
995 "Name of a file at the top of the tree, used to find the top. "
996 "Only needed for SVN and CVS."],
997 ["topdir", None, None,
998 "Path to the top of the working copy. Only needed for SVN and CVS."],
999 ]
1000
1001 optFlags = [
1002 ["wait", None,
1003 "wait until the builds have finished"],
1004 ["dryrun", 'n',
1005 "Gather info, but don't actually submit."],
1006 ["get-builder-names", None,
1007 "Get the names of available builders. Doesn't submit anything. "
1008 "Only supported for 'pb' connections."],
1009 ["quiet", "q",
1010 "Don't print status of current builds while waiting."],
1011 ]
1012
1013
1014 buildbotOptions = [
1015 [ 'try_connect', 'connect' ],
1016
1017 [ 'try_vc', 'vc' ],
1018 [ 'try_branch', 'branch' ],
1019 [ 'try_repository', 'repository' ],
1020 [ 'try_topdir', 'topdir' ],
1021 [ 'try_topfile', 'topfile' ],
1022 [ 'try_host', 'host' ],
1023 [ 'try_username', 'username' ],
1024 [ 'try_jobdir', 'jobdir' ],
1025 [ 'try_passwd', 'passwd' ],
1026 [ 'try_master', 'master' ],
1027 [ 'try_who', 'who' ],
1028 [ 'try_comment', 'comment' ],
1029
1030
1031
1032
1033 [ 'try_masterstatus', 'master' ],
1034 [ 'try_dir', 'jobdir' ],
1035 [ 'try_password', 'passwd' ],
1036 ]
1037
1042
1044 self['builders'].append(option)
1045
1047
1048 propertylist = option.split(",")
1049 for i in range(0,len(propertylist)):
1050 splitproperty = propertylist[i].split("=", 1)
1051 self['properties'][splitproperty[0]] = splitproperty[1]
1052
1054 self['patchlevel'] = int(option)
1055
1057 return "Usage: buildbot try [options]"
1058
1059 - def postOptions(self):
1060 opts = loadOptionsFile()
1061 if not self['builders']:
1062 self['builders'] = opts.get('try_builders', [])
1063 if opts.get('try_wait', False):
1064 self['wait'] = True
1065 if opts.get('try_quiet', False):
1066 self['quiet'] = True
1067
1068
1069 if not self['master']:
1070 self['master'] = opts.get('masterstatus', None)
1071
1076
1078 optParameters = [
1079 ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"],
1080 ]
1082 return "Usage: buildbot tryserver [options]"
1083
1086 try:
1087 from hashlib import md5
1088 assert md5
1089 except ImportError:
1090
1091 import md5
1092 jobdir = os.path.expanduser(config["jobdir"])
1093 job = sys.stdin.read()
1094
1095
1096
1097 timestring = "%d" % time.time()
1098 try:
1099 m = md5()
1100 except TypeError:
1101
1102 m = md5.new()
1103 m.update(job)
1104 jobhash = m.hexdigest()
1105 fn = "%s-%s" % (timestring, jobhash)
1106 tmpfile = os.path.join(jobdir, "tmp", fn)
1107 newfile = os.path.join(jobdir, "new", fn)
1108 f = open(tmpfile, "w")
1109 f.write(job)
1110 f.close()
1111 os.rename(tmpfile, newfile)
1112
1115 optFlags = [
1116 ['quiet', 'q', "Don't display error messages or tracebacks"],
1117 ]
1118
1120 return "Usage: buildbot checkconfig [configFile]\n" + \
1121 " If not specified, 'master.cfg' will be used as 'configFile'"
1122
1124 if len(args) >= 1:
1125 self['configFile'] = args[0]
1126 else:
1127 self['configFile'] = 'master.cfg'
1128
1147 def eb(f):
1148 if not quiet:
1149 f.printTraceback()
1150 return False
1151 d.addCallbacks(cb, eb)
1152
1153 return d
1154
1156 synopsis = "Usage: buildbot <command> [command options]"
1157
1158 subCommands = [
1159
1160 ['create-master', None, MasterOptions,
1161 "Create and populate a directory for a new buildmaster"],
1162 ['upgrade-master', None, UpgradeMasterOptions,
1163 "Upgrade an existing buildmaster directory for the current version"],
1164 ['start', None, StartOptions, "Start a buildmaster"],
1165 ['stop', None, StopOptions, "Stop a buildmaster"],
1166 ['restart', None, RestartOptions,
1167 "Restart a buildmaster"],
1168
1169 ['reconfig', None, ReconfigOptions,
1170 "SIGHUP a buildmaster to make it re-read the config file"],
1171 ['sighup', None, ReconfigOptions,
1172 "SIGHUP a buildmaster to make it re-read the config file"],
1173
1174 ['sendchange', None, SendChangeOptions,
1175 "Send a change to the buildmaster"],
1176
1177 ['debugclient', None, DebugClientOptions,
1178 "Launch a small debug panel GUI"],
1179
1180 ['statuslog', None, StatusLogOptions,
1181 "Emit current builder status to stdout"],
1182 ['statusgui', None, StatusGuiOptions,
1183 "Display a small window showing current builder status"],
1184
1185
1186 ['try', None, TryOptions, "Run a build with your local changes"],
1187
1188 ['tryserver', None, TryServerOptions,
1189 "buildmaster-side 'try' support function, not for users"],
1190
1191 ['checkconfig', None, CheckConfigOptions,
1192 "test the validity of a master.cfg config file"],
1193
1194
1195 ]
1196
1201
1203 from twisted.python import log
1204 log.startLogging(sys.stderr)
1205
1206 - def postOptions(self):
1207 if not hasattr(self, 'subOptions'):
1208 raise usage.UsageError("must specify a command")
1209
1267