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
35
37 """Find the .buildbot/FILENAME file. Crawl from the current directory up
38 towards the root, and also look in ~/.buildbot . The first directory
39 that's owned by the user and has the file we're looking for wins. Windows
40 skips the owned-by-user test.
41
42 @rtype: dict
43 @return: a dictionary of names defined in the options file. If no options
44 file was found, return an empty dict.
45 """
46
47 if here is None:
48 here = os.getcwd()
49 here = os.path.abspath(here)
50
51 if home is None:
52 if runtime.platformType == 'win32':
53
54 from win32com.shell import shellcon, shell
55 appdata = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)
56 home = os.path.join(appdata, "buildbot")
57 else:
58 home = os.path.expanduser("~/.buildbot")
59
60 searchpath = []
61 toomany = 20
62 while True:
63 searchpath.append(os.path.join(here, ".buildbot"))
64 next = os.path.dirname(here)
65 if next == here:
66 break
67 here = next
68 toomany -= 1
69 if toomany == 0:
70 raise ValueError("Hey, I seem to have wandered up into the "
71 "infinite glories of the heavens. Oops.")
72 searchpath.append(home)
73
74 localDict = {}
75
76 for d in searchpath:
77 if os.path.isdir(d):
78 if runtime.platformType != 'win32':
79 if os.stat(d)[stat.ST_UID] != os.getuid():
80 print "skipping %s because you don't own it" % d
81 continue
82 optfile = os.path.join(d, filename)
83 if os.path.exists(optfile):
84 try:
85 f = open(optfile, "r")
86 options = f.read()
87 exec options in localDict
88 except:
89 print "error while reading %s" % optfile
90 raise
91 break
92
93 for k in localDict.keys():
94 if k.startswith("__"):
95 del localDict[k]
96 return localDict
97
99 optFlags = [
100 ['help', 'h', "Display this message"],
101 ["quiet", "q", "Do not emit the commands being run"],
102 ]
103
104 longdesc = """
105 Operates upon the specified <basedir> (or the current directory, if not
106 specified).
107 """
108
109 opt_h = usage.Options.opt_help
110
112 if len(args) > 0:
113 self['basedir'] = args[0]
114 else:
115
116 self['basedir'] = os.getcwd()
117 if len(args) > 1:
118 raise usage.UsageError("I wasn't expecting so many arguments")
119
120 - def postOptions(self):
121 self['basedir'] = os.path.abspath(self['basedir'])
122
123 makefile_sample = """# -*- makefile -*-
124
125 # This is a simple makefile which lives in a buildmaster/buildslave
126 # directory (next to the buildbot.tac file). It allows you to start/stop the
127 # master or slave by doing 'make start' or 'make stop'.
128
129 # The 'reconfig' target will tell a buildmaster to reload its config file.
130
131 start:
132 twistd --no_save -y buildbot.tac
133
134 stop:
135 if [ -e twistd.pid ]; \\
136 then kill `cat twistd.pid`; \\
137 else echo "Nothing to stop."; \\
138 fi
139
140 reconfig:
141 if [ -e twistd.pid ]; \\
142 then kill -HUP `cat twistd.pid`; \\
143 else echo "Nothing to reconfig."; \\
144 fi
145
146 log:
147 if [ -e twistd.log ]; \\
148 then tail -f twistd.log; \\
149 else echo "Nothing to tail."; \\
150 fi
151 """
152
159
167
169 path = os.path.join(self.basedir, "info")
170 if not os.path.exists(path):
171 if not self.quiet: print "mkdir", path
172 os.mkdir(path)
173 created = False
174 admin = os.path.join(path, "admin")
175 if not os.path.exists(admin):
176 if not self.quiet:
177 print "Creating info/admin, you need to edit it appropriately"
178 f = open(admin, "wt")
179 f.write("Your Name Here <admin@youraddress.invalid>\n")
180 f.close()
181 created = True
182 host = os.path.join(path, "host")
183 if not os.path.exists(host):
184 if not self.quiet:
185 print "Creating info/host, you need to edit it appropriately"
186 f = open(host, "wt")
187 f.write("Please put a description of this build host here\n")
188 f.close()
189 created = True
190 access_uri = os.path.join(path, "access_uri")
191 if not os.path.exists(access_uri):
192 if not self.quiet:
193 print "Not creating info/access_uri - add it if you wish"
194 if created and not self.quiet:
195 print "Please edit the files in %s appropriately." % path
196
200
201 - def makeTAC(self, contents, secret=False):
202 tacfile = "buildbot.tac"
203 if os.path.exists(tacfile):
204 oldcontents = open(tacfile, "rt").read()
205 if oldcontents == contents:
206 if not self.quiet:
207 print "buildbot.tac already exists and is correct"
208 return
209 if not self.quiet:
210 print "not touching existing buildbot.tac"
211 print "creating buildbot.tac.new instead"
212 tacfile = "buildbot.tac.new"
213 f = open(tacfile, "wt")
214 f.write(contents)
215 f.close()
216 if secret:
217 os.chmod(tacfile, 0600)
218
220 target = "Makefile.sample"
221 if os.path.exists(target):
222 oldcontents = open(target, "rt").read()
223 if oldcontents == makefile_sample:
224 if not self.quiet:
225 print "Makefile.sample already exists and is correct"
226 return
227 if not self.quiet:
228 print "replacing Makefile.sample"
229 else:
230 if not self.quiet:
231 print "creating Makefile.sample"
232 f = open(target, "wt")
233 f.write(makefile_sample)
234 f.close()
235
237 target = "master.cfg.sample"
238 config_sample = open(source, "rt").read()
239 if os.path.exists(target):
240 oldcontents = open(target, "rt").read()
241 if oldcontents == config_sample:
242 if not self.quiet:
243 print "master.cfg.sample already exists and is up-to-date"
244 return
245 if not self.quiet:
246 print "replacing master.cfg.sample"
247 else:
248 if not self.quiet:
249 print "creating master.cfg.sample"
250 f = open(target, "wt")
251 f.write(config_sample)
252 f.close()
253 os.chmod(target, 0600)
254
256 webdir = os.path.join(self.basedir, "public_html")
257 if os.path.exists(webdir):
258 if not self.quiet:
259 print "public_html/ already exists: not replacing"
260 return
261 else:
262 os.mkdir(webdir)
263 if not self.quiet:
264 print "populating public_html/"
265 for target, source in files.iteritems():
266 target = os.path.join(webdir, target)
267 f = open(target, "wt")
268 f.write(open(source, "rt").read())
269 f.close()
270
281
283 new_contents = open(source, "rt").read()
284 if os.path.exists(target):
285 old_contents = open(target, "rt").read()
286 if old_contents != new_contents:
287 if overwrite:
288 if not self.quiet:
289 print "%s has old/modified contents" % target
290 print " overwriting it with new contents"
291 open(target, "wt").write(new_contents)
292 else:
293 if not self.quiet:
294 print "%s has old/modified contents" % target
295 print " writing new contents to %s.new" % target
296 open(target + ".new", "wt").write(new_contents)
297
298 else:
299 if not self.quiet:
300 print "populating %s" % target
301 open(target, "wt").write(new_contents)
302
304 if os.path.exists(source):
305 if os.path.exists(dest):
306 print "Notice: %s now overrides %s" % (dest, source)
307 print " as the latter is not used by buildbot anymore."
308 print " Decide which one you want to keep."
309 else:
310 try:
311 print "Notice: Moving %s to %s." % (source, dest)
312 print " You can (and probably want to) remove it if you haven't modified this file."
313 os.renames(source, dest)
314 except Exception, e:
315 print "Error moving %s to %s: %s" % (source, dest, str(e))
316
326
328 from buildbot.master import BuildMaster
329 from twisted.python import log, failure
330
331 master_cfg = os.path.join(self.basedir, "master.cfg")
332 if not os.path.exists(master_cfg):
333 if not self.quiet:
334 print "No master.cfg found"
335 return 1
336
337
338
339
340
341
342
343
344
345
346
347
348
349 if sys.path[0] != self.basedir:
350 sys.path.insert(0, self.basedir)
351
352 m = BuildMaster(self.basedir)
353
354
355
356
357 messages = []
358 log.addObserver(messages.append)
359 try:
360
361
362
363
364 m.loadConfig(open(master_cfg, "r"), check_synchronously_only=True)
365 except:
366 f = failure.Failure()
367 if not self.quiet:
368 print
369 for m in messages:
370 print "".join(m['message'])
371 print f
372 print
373 print "An error was detected in the master.cfg file."
374 print "Please correct the problem and run 'buildbot upgrade-master' again."
375 print
376 return 1
377 return 0
378
379 DB_HELP = """
380 The --db string is evaluated to build the DB object, which specifies
381 which database the buildmaster should use to hold scheduler state and
382 status information. The default (which creates an SQLite database in
383 BASEDIR/state.sqlite) is equivalent to:
384
385 --db='DBSpec("sqlite3", basedir+"/state.sqlite"))'
386 --db='sqlite:///state.sqlite'
387
388 To use a remote MySQL database instead, use something like:
389
390 --db='mysql://bbuser:bbpasswd@dbhost/bbdb'
391 """
392
394 optFlags = [
395 ["replace", "r", "Replace any modified files without confirmation."],
396 ]
397 optParameters = [
398 ["db", None, "sqlite:///state.sqlite",
399 "which DB to use for scheduler/status state. See below for syntax."],
400 ]
401
403 return "Usage: buildbot upgrade-master [options] [<basedir>]"
404
405 longdesc = """
406 This command takes an existing buildmaster working directory and
407 adds/modifies the files there to work with the current version of
408 buildbot. When this command is finished, the buildmaster directory should
409 look much like a brand-new one created by the 'create-master' command.
410
411 Use this after you've upgraded your buildbot installation and before you
412 restart the buildmaster to use the new version.
413
414 If you have modified the files in your working directory, this command
415 will leave them untouched, but will put the new recommended contents in a
416 .new file (for example, if index.html has been modified, this command
417 will create index.html.new). You can then look at the new version and
418 decide how to merge its contents into your modified file.
419 """+DB_HELP+"""
420 When upgrading from a pre-0.8.0 release (which did not use a database),
421 this command will create the given database and migrate data from the old
422 pickle files into it, then move the pickle files out of the way (e.g. to
423 changes.pck.old). To revert to an older release, rename the pickle files
424 back. When you are satisfied with the new version, you can delete the old
425 pickle files.
426 """
427
429 basedir = os.path.expanduser(config['basedir'])
430 m = Maker(config)
431
432
433
434 m.upgrade_public_html({
435 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"),
436 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"),
437 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"),
438 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"),
439 })
440 m.populate_if_missing(os.path.join(basedir, "master.cfg.sample"),
441 util.sibpath(__file__, "sample.cfg"),
442 overwrite=True)
443
444 m.move_if_present(os.path.join(basedir, "public_html/index.html"),
445 os.path.join(basedir, "templates/root.html"))
446
447 from buildbot.db import schema, connector, dbspec
448 spec = dbspec.DBSpec.from_url(config["db"], basedir)
449
450
451
452 sm = schema.DBSchemaManager(spec, basedir)
453 sm.upgrade()
454
455 db = connector.DBConnector(spec)
456 db.start()
457
458 rc = m.check_master_cfg()
459 if rc:
460 return rc
461 if not config['quiet']: print "upgrade complete"
462 return 0
463
464
466 optFlags = [
467 ["force", "f",
468 "Re-use an existing directory (will not overwrite master.cfg file)"],
469 ["relocatable", "r",
470 "Create a relocatable buildbot.tac"],
471 ]
472 optParameters = [
473 ["config", "c", "master.cfg", "name of the buildmaster config file"],
474 ["log-size", "s", "1000000",
475 "size at which to rotate twisted log files"],
476 ["log-count", "l", "None",
477 "limit the number of kept old twisted log files"],
478 ["db", None, "sqlite:///state.sqlite",
479 "which DB to use for scheduler/status state. See below for syntax."],
480 ]
482 return "Usage: buildbot create-master [options] [<basedir>]"
483
484 longdesc = """
485 This command creates a buildmaster working directory and buildbot.tac file.
486 The master will live in <dir> and create various files there. If
487 --relocatable is given, then the resulting buildbot.tac file will be
488 written such that its containing directory is assumed to be the basedir.
489 This is generally a good idea.
490
491 At runtime, the master will read a configuration file (named
492 'master.cfg' by default) in its basedir. This file should contain python
493 code which eventually defines a dictionary named 'BuildmasterConfig'.
494 The elements of this dictionary are used to configure the Buildmaster.
495 See doc/config.xhtml for details about what can be controlled through
496 this interface.
497 """ + DB_HELP + """
498 The --db string is stored verbatim in the buildbot.tac file, and
499 evaluated as 'buildbot start' time to pass a DBConnector instance into
500 the newly-created BuildMaster object.
501 """
502
503 - def postOptions(self):
504 MakerBase.postOptions(self)
505 if not re.match('^\d+$', self['log-size']):
506 raise usage.UsageError("log-size parameter needs to be an int")
507 if not re.match('^\d+$', self['log-count']) and \
508 self['log-count'] != 'None':
509 raise usage.UsageError("log-count parameter needs to be an int "+
510 " or None")
511
512
513 masterTAC = """
514 import os
515
516 from twisted.application import service
517 from buildbot.master import BuildMaster
518
519 basedir = r'%(basedir)s'
520 rotateLength = %(log-size)s
521 maxRotatedFiles = %(log-count)s
522
523 # if this is a relocatable tac file, get the directory containing the TAC
524 if basedir == '.':
525 import os.path
526 basedir = os.path.abspath(os.path.dirname(__file__))
527
528 application = service.Application('buildmaster')
529 try:
530 from twisted.python.logfile import LogFile
531 from twisted.python.log import ILogObserver, FileLogObserver
532 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
533 maxRotatedFiles=maxRotatedFiles)
534 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
535 except ImportError:
536 # probably not yet twisted 8.2.0 and beyond, can't set log yet
537 pass
538
539 configfile = r'%(config)s'
540
541 m = BuildMaster(basedir, configfile)
542 m.setServiceParent(application)
543 m.log_rotation.rotateLength = rotateLength
544 m.log_rotation.maxRotatedFiles = maxRotatedFiles
545
546 """
547
549 m = Maker(config)
550 m.mkdir()
551 m.chdir()
552 if config['relocatable']:
553 config['basedir'] = '.'
554 contents = masterTAC % config
555 m.makeTAC(contents)
556 m.sampleconfig(util.sibpath(__file__, "sample.cfg"))
557 m.public_html({
558 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"),
559 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"),
560 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"),
561 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"),
562 })
563 m.makefile()
564 m.create_db()
565
566 if not m.quiet: print "buildmaster configured in %s" % m.basedir
567
569 optFlags = [
570 ["force", "f", "Re-use an existing directory"],
571 ["relocatable", "r",
572 "Create a relocatable buildbot.tac"],
573 ]
574 optParameters = [
575
576
577
578
579
580
581 ["keepalive", "k", 600,
582 "Interval at which keepalives should be sent (in seconds)"],
583 ["usepty", None, 0,
584 "(1 or 0) child processes should be run in a pty (default 0)"],
585 ["umask", None, "None",
586 "controls permissions of generated files. Use --umask=022 to be world-readable"],
587 ["maxdelay", None, 300,
588 "Maximum time between connection attempts"],
589 ["log-size", "s", "1000000",
590 "size at which to rotate twisted log files"],
591 ["log-count", "l", "None",
592 "limit the number of kept old twisted log files"],
593 ]
594
595 longdesc = """
596 This command creates a buildslave working directory and buildbot.tac
597 file. The bot will use the <name> and <passwd> arguments to authenticate
598 itself when connecting to the master. All commands are run in a
599 build-specific subdirectory of <basedir>. <master> is a string of the
600 form 'hostname:port', and specifies where the buildmaster can be reached.
601
602 <name>, <passwd>, and <master> will be provided by the buildmaster
603 administrator for your bot. You must choose <basedir> yourself.
604 """
605
607 return "Usage: buildbot create-slave [options] <basedir> <master> <name> <passwd>"
608
610 if len(args) < 4:
611 raise usage.UsageError("command needs more arguments")
612 basedir, master, name, passwd = args
613 if master[:5] == "http:":
614 raise usage.UsageError("<master> is not a URL - do not use URL")
615 self['basedir'] = basedir
616 self['master'] = master
617 self['name'] = name
618 self['passwd'] = passwd
619
620 - def postOptions(self):
621 MakerBase.postOptions(self)
622 self['usepty'] = int(self['usepty'])
623 self['keepalive'] = int(self['keepalive'])
624 self['maxdelay'] = int(self['maxdelay'])
625 if self['master'].find(":") == -1:
626 raise usage.UsageError("--master must be in the form host:portnum")
627 if not re.match('^\d+$', self['log-size']):
628 raise usage.UsageError("log-size parameter needs to be an int")
629 if not re.match('^\d+$', self['log-count']) and \
630 self['log-count'] != 'None':
631 raise usage.UsageError("log-count parameter needs to be an int "+
632 " or None")
633
634 slaveTAC = """
635 import os
636
637 from twisted.application import service
638 from buildbot.slave.bot import BuildSlave
639
640 basedir = r'%(basedir)s'
641 rotateLength = %(log-size)s
642 maxRotatedFiles = %(log-count)s
643
644 # if this is a relocatable tac file, get the directory containing the TAC
645 if basedir == '.':
646 import os.path
647 basedir = os.path.abspath(os.path.dirname(__file__))
648
649 application = service.Application('buildslave')
650 try:
651 from twisted.python.logfile import LogFile
652 from twisted.python.log import ILogObserver, FileLogObserver
653 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
654 maxRotatedFiles=maxRotatedFiles)
655 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
656 except ImportError:
657 # probably not yet twisted 8.2.0 and beyond, can't set log yet
658 pass
659
660 buildmaster_host = '%(host)s'
661 port = %(port)d
662 slavename = '%(name)s'
663 passwd = '%(passwd)s'
664 keepalive = %(keepalive)d
665 usepty = %(usepty)d
666 umask = %(umask)s
667 maxdelay = %(maxdelay)d
668
669 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir,
670 keepalive, usepty, umask=umask, maxdelay=maxdelay)
671 s.setServiceParent(application)
672
673 """
674
698
699
700
701 -def stop(config, signame="TERM", wait=False):
702 import signal
703 basedir = config['basedir']
704 quiet = config['quiet']
705 os.chdir(basedir)
706 try:
707 f = open("twistd.pid", "rt")
708 except:
709 raise BuildbotNotRunningError
710 pid = int(f.read().strip())
711 signum = getattr(signal, "SIG"+signame)
712 timer = 0
713 try:
714 os.kill(pid, signum)
715 except OSError, e:
716 if e.errno != 3:
717 raise
718
719 if not wait:
720 if not quiet:
721 print "sent SIG%s to process" % signame
722 return
723 time.sleep(0.1)
724 while timer < 10:
725
726 try:
727 os.kill(pid, 0)
728 except OSError:
729 if not quiet:
730 print "buildbot process %d is dead" % pid
731 return
732 timer += 1
733 time.sleep(1)
734 if not quiet:
735 print "never saw process go away"
736
747
748
750 optFlags = [
751 ['quiet', 'q', "Don't display startup log messages"],
752 ]
754 return "Usage: buildbot start [<basedir>]"
755
758 return "Usage: buildbot stop [<basedir>]"
759
761 optFlags = [
762 ['quiet', 'q', "Don't display log messages about reconfiguration"],
763 ]
765 return "Usage: buildbot reconfig [<basedir>]"
766
767
768
770 optFlags = [
771 ['quiet', 'q', "Don't display startup log messages"],
772 ]
774 return "Usage: buildbot restart [<basedir>]"
775
777 optFlags = [
778 ['help', 'h', "Display this message"],
779 ]
780 optParameters = [
781 ["master", "m", None,
782 "Location of the buildmaster's slaveport (host:port)"],
783 ["passwd", "p", None, "Debug password to use"],
784 ]
785 buildbotOptions = [
786 [ 'debugMaster', 'passwd' ],
787 [ 'master', 'master' ],
788 ]
789
791 if len(args) > 0:
792 self['master'] = args[0]
793 if len(args) > 1:
794 self['passwd'] = args[1]
795 if len(args) > 2:
796 raise usage.UsageError("I wasn't expecting so many arguments")
797
813
815 optFlags = [
816 ['help', 'h', "Display this message"],
817 ]
818 optParameters = [
819 ["master", "m", None,
820 "Location of the buildmaster's status port (host:port)"],
821 ["username", "u", "statusClient", "Username performing the trial build"],
822 ["passwd", None, "clientpw", "password for PB authentication"],
823 ]
824 buildbotOptions = [
825 [ 'masterstatus', 'master' ],
826 ]
827
829 if len(args) > 0:
830 self['master'] = args[0]
831 if len(args) > 1:
832 raise usage.UsageError("I wasn't expecting so many arguments")
833
844
853
858
859 optParameters = [
860 ("master", "m", None,
861 "Location of the buildmaster's PBListener (host:port)"),
862 ("username", "u", None, "Username performing the commit"),
863 ("repository", "R", None, "Repository specifier"),
864 ("project", "P", None, "Project specifier"),
865 ("branch", "b", None, "Branch specifier"),
866 ("category", "c", None, "Category of repository"),
867 ("revision", "r", None, "Revision specifier"),
868 ("revision_file", None, None, "Filename containing revision spec"),
869 ("property", "p", None,
870 "A property for the change, in the format: name:value"),
871 ("comments", "m", None, "log message"),
872 ("logfile", "F", None,
873 "Read the log messages from this file (- for stdin)"),
874 ("when", "w", None, "timestamp to use as the change time"),
875 ]
876
877 buildbotOptions = [
878 [ 'master', 'master' ],
879 [ 'username', 'username' ],
880 [ 'branch', 'branch' ],
881 [ 'category', 'category' ],
882 ]
883
885 return "Usage: buildbot sendchange [options] filenames.."
889 name,value = property.split(':')
890 self['properties'][name] = value
891
892
894 """Send a single change to the buildmaster's PBChangeSource. The
895 connection will be drpoped as soon as the Change has been sent."""
896 from buildbot.clients.sendchange import Sender
897
898 user = config.get('username')
899 master = config.get('master')
900 branch = config.get('branch')
901 category = config.get('category')
902 revision = config.get('revision')
903 properties = config.get('properties', {})
904 repository = config.get('repository', '')
905 project = config.get('project', '')
906 if config.get('when'):
907 when = float(config.get('when'))
908 else:
909 when = None
910 if config.get("revision_file"):
911 revision = open(config["revision_file"],"r").read()
912
913 comments = config.get('comments')
914 if not comments and config.get('logfile'):
915 if config['logfile'] == "-":
916 f = sys.stdin
917 else:
918 f = open(config['logfile'], "rt")
919 comments = f.read()
920 if comments is None:
921 comments = ""
922
923 files = config.get('files', [])
924
925 assert user, "you must provide a username"
926 assert master, "you must provide the master location"
927
928 s = Sender(master, user)
929 d = s.send(branch, revision, comments, files, category=category, when=when,
930 properties=properties, repository=repository, project=project)
931 if runReactor:
932 d.addCallbacks(s.printSuccess, s.printFailure)
933 d.addBoth(s.stop)
934 s.run()
935 return d
936
937
939 optParameters = [
940 ["builder", None, None, "which Builder to start"],
941 ["branch", None, None, "which branch to build"],
942 ["revision", None, None, "which revision to build"],
943 ["reason", None, None, "the reason for starting the build"],
944 ]
945
947 args = list(args)
948 if len(args) > 0:
949 if self['builder'] is not None:
950 raise usage.UsageError("--builder provided in two ways")
951 self['builder'] = args.pop(0)
952 if len(args) > 0:
953 if self['reason'] is not None:
954 raise usage.UsageError("--reason provided in two ways")
955 self['reason'] = " ".join(args)
956
957
959 optParameters = [
960 ["connect", "c", None,
961 "how to reach the buildmaster, either 'ssh' or 'pb'"],
962
963 ["tryhost", None, None,
964 "the hostname (used by ssh) for the buildmaster"],
965 ["trydir", None, None,
966 "the directory (on the tryhost) where tryjobs are deposited"],
967 ["username", "u", None, "Username performing the trial build"],
968
969 ["master", "m", None,
970 "Location of the buildmaster's PBListener (host:port)"],
971 ["passwd", None, None, "password for PB authentication"],
972
973 ["diff", None, None,
974 "Filename of a patch to use instead of scanning a local tree. Use '-' for stdin."],
975 ["patchlevel", "p", 0,
976 "Number of slashes to remove from patch pathnames, like the -p option to 'patch'"],
977
978 ["baserev", None, None,
979 "Base revision to use instead of scanning a local tree."],
980
981 ["vc", None, None,
982 "The VC system in use, one of: cvs,svn,tla,baz,darcs,p4"],
983 ["branch", None, None,
984 "The branch in use, for VC systems that can't figure it out"
985 " themselves"],
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, format:prop=value,propb=valueb..."],
991
992 ["try-topfile", None, None,
993 "Name of a file at the top of the tree, used to find the top. Only needed for SVN and CVS."],
994 ["try-topdir", None, None,
995 "Path to the top of the working copy. Only needed for SVN and CVS."],
996
997 ]
998
999 optFlags = [
1000 ["wait", None, "wait until the builds have finished"],
1001 ["dryrun", 'n', "Gather info, but don't actually submit."],
1002 ["get-builder-names", None, "Get the names of available builders. Doesn't submit anything. Only supported for 'pb' connections."],
1003 ]
1004
1005
1006
1007 buildbotOptions = [
1008 [ 'try_connect', 'connect' ],
1009
1010 [ 'try_vc', 'vc' ],
1011 [ 'try_branch', 'branch' ],
1012 [ 'try_topdir', 'try-topdir' ],
1013 [ 'try_topfile', 'try-topfile' ],
1014 [ 'try_host', 'tryhost' ],
1015 [ 'try_username', 'username' ],
1016 [ 'try_dir', 'trydir' ],
1017 [ 'try_password', 'passwd' ],
1018 [ 'try_master', 'master' ],
1019
1020 [ 'masterstatus', 'master' ],
1021 ]
1022
1027
1029 self['builders'].append(option)
1030
1032
1033 properties = {}
1034 propertylist = option.split(",")
1035 for i in range(0,len(propertylist)):
1036 print propertylist[i]
1037 splitproperty = propertylist[i].split("=")
1038 properties[splitproperty[0]] = splitproperty[1]
1039 self['properties'] = properties
1040
1042 self['patchlevel'] = int(option)
1043
1045 return "Usage: buildbot try [options]"
1046
1047 - def postOptions(self):
1048 opts = loadOptionsFile()
1049 if not self['builders']:
1050 self['builders'] = opts.get('try_builders', [])
1051 if opts.get('try_wait', False):
1052 self['wait'] = True
1053
1058
1060 optParameters = [
1061 ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"],
1062 ]
1063
1065 import md5
1066 jobdir = os.path.expanduser(config["jobdir"])
1067 job = sys.stdin.read()
1068
1069
1070
1071 timestring = "%d" % time.time()
1072 jobhash = md5.new(job).hexdigest()
1073 fn = "%s-%s" % (timestring, jobhash)
1074 tmpfile = os.path.join(jobdir, "tmp", fn)
1075 newfile = os.path.join(jobdir, "new", fn)
1076 f = open(tmpfile, "w")
1077 f.write(job)
1078 f.close()
1079 os.rename(tmpfile, newfile)
1080
1081
1083 optFlags = [
1084 ['quiet', 'q', "Don't display error messages or tracebacks"],
1085 ]
1086
1088 return "Usage :buildbot checkconfig [configFile]\n" + \
1089 " If not specified, 'master.cfg' will be used as 'configFile'"
1090
1092 if len(args) >= 1:
1093 self['configFile'] = args[0]
1094 else:
1095 self['configFile'] = 'master.cfg'
1096
1097
1116
1117
1119 synopsis = "Usage: buildbot <command> [command options]"
1120
1121 subCommands = [
1122
1123 ['create-master', None, MasterOptions,
1124 "Create and populate a directory for a new buildmaster"],
1125 ['upgrade-master', None, UpgradeMasterOptions,
1126 "Upgrade an existing buildmaster directory for the current version"],
1127 ['create-slave', None, SlaveOptions,
1128 "Create and populate a directory for a new buildslave"],
1129 ['start', None, StartOptions, "Start a buildmaster or buildslave"],
1130 ['stop', None, StopOptions, "Stop a buildmaster or buildslave"],
1131 ['restart', None, RestartOptions,
1132 "Restart a buildmaster or buildslave"],
1133
1134 ['reconfig', None, ReconfigOptions,
1135 "SIGHUP a buildmaster to make it re-read the config file"],
1136 ['sighup', None, ReconfigOptions,
1137 "SIGHUP a buildmaster to make it re-read the config file"],
1138
1139 ['sendchange', None, SendChangeOptions,
1140 "Send a change to the buildmaster"],
1141
1142 ['debugclient', None, DebugClientOptions,
1143 "Launch a small debug panel GUI"],
1144
1145 ['statuslog', None, StatusClientOptions,
1146 "Emit current builder status to stdout"],
1147 ['statusgui', None, StatusClientOptions,
1148 "Display a small window showing current builder status"],
1149
1150
1151 ['try', None, TryOptions, "Run a build with your local changes"],
1152
1153 ['tryserver', None, TryServerOptions,
1154 "buildmaster-side 'try' support function, not for users"],
1155
1156 ['checkconfig', None, CheckConfigOptions,
1157 "test the validity of a master.cfg config file"],
1158
1159
1160 ]
1161
1166
1168 from twisted.python import log
1169 log.startLogging(sys.stderr)
1170
1171 - def postOptions(self):
1172 if not hasattr(self, 'subOptions'):
1173 raise usage.UsageError("must specify a command")
1174
1175
1221