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
12 buildbot_tac = os.path.join(dir, "buildbot.tac")
13 if not os.path.isfile(buildbot_tac):
14 print "no buildbot.tac"
15 return False
16
17 contents = open(buildbot_tac, "r").read()
18 return "Application('buildmaster')" in contents
19
20
21
22
23
24
25
44
46 """Find the .buildbot/FILENAME file. Crawl from the current directory up
47 towards the root, and also look in ~/.buildbot . The first directory
48 that's owned by the user and has the file we're looking for wins. Windows
49 skips the owned-by-user test.
50
51 @rtype: dict
52 @return: a dictionary of names defined in the options file. If no options
53 file was found, return an empty dict.
54 """
55
56 if here is None:
57 here = os.getcwd()
58 here = os.path.abspath(here)
59
60 if home is None:
61 if runtime.platformType == 'win32':
62
63 from win32com.shell import shellcon, shell
64 appdata = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)
65 home = os.path.join(appdata, "buildbot")
66 else:
67 home = os.path.expanduser("~/.buildbot")
68
69 searchpath = []
70 toomany = 20
71 while True:
72 searchpath.append(os.path.join(here, ".buildbot"))
73 next = os.path.dirname(here)
74 if next == here:
75 break
76 here = next
77 toomany -= 1
78 if toomany == 0:
79 raise ValueError("Hey, I seem to have wandered up into the "
80 "infinite glories of the heavens. Oops.")
81 searchpath.append(home)
82
83 localDict = {}
84
85 for d in searchpath:
86 if os.path.isdir(d):
87 if runtime.platformType != 'win32':
88 if os.stat(d)[stat.ST_UID] != os.getuid():
89 print "skipping %s because you don't own it" % d
90 continue
91 optfile = os.path.join(d, filename)
92 if os.path.exists(optfile):
93 try:
94 f = open(optfile, "r")
95 options = f.read()
96 exec options in localDict
97 except:
98 print "error while reading %s" % optfile
99 raise
100 break
101
102 for k in localDict.keys():
103 if k.startswith("__"):
104 del localDict[k]
105 return localDict
106
108 optFlags = [
109 ['help', 'h', "Display this message"],
110 ["quiet", "q", "Do not emit the commands being run"],
111 ]
112
113 longdesc = """
114 Operates upon the specified <basedir> (or the current directory, if not
115 specified).
116 """
117
118 opt_h = usage.Options.opt_help
119
121 if len(args) > 0:
122 self['basedir'] = args[0]
123 else:
124
125 self['basedir'] = os.getcwd()
126 if len(args) > 1:
127 raise usage.UsageError("I wasn't expecting so many arguments")
128
129 - def postOptions(self):
130 self['basedir'] = os.path.abspath(self['basedir'])
131
132 makefile_sample = """# -*- makefile -*-
133
134 # This is a simple makefile which lives in a buildmaster
135 # directory (next to the buildbot.tac file). It allows you to start/stop the
136 # master by doing 'make start' or 'make stop'.
137
138 # The 'reconfig' target will tell a buildmaster to reload its config file.
139
140 start:
141 twistd --no_save -y buildbot.tac
142
143 stop:
144 if [ -e twistd.pid ]; \\
145 then kill `cat twistd.pid`; \\
146 else echo "Nothing to stop."; \\
147 fi
148
149 reconfig:
150 if [ -e twistd.pid ]; \\
151 then kill -HUP `cat twistd.pid`; \\
152 else echo "Nothing to reconfig."; \\
153 fi
154
155 log:
156 if [ -e twistd.log ]; \\
157 then tail -f twistd.log; \\
158 else echo "Nothing to tail."; \\
159 fi
160 """
161
168
176
178 path = os.path.join(self.basedir, "info")
179 if not os.path.exists(path):
180 if not self.quiet: print "mkdir", path
181 os.mkdir(path)
182 created = False
183 admin = os.path.join(path, "admin")
184 if not os.path.exists(admin):
185 if not self.quiet:
186 print "Creating info/admin, you need to edit it appropriately"
187 f = open(admin, "wt")
188 f.write("Your Name Here <admin@youraddress.invalid>\n")
189 f.close()
190 created = True
191 host = os.path.join(path, "host")
192 if not os.path.exists(host):
193 if not self.quiet:
194 print "Creating info/host, you need to edit it appropriately"
195 f = open(host, "wt")
196 f.write("Please put a description of this build host here\n")
197 f.close()
198 created = True
199 access_uri = os.path.join(path, "access_uri")
200 if not os.path.exists(access_uri):
201 if not self.quiet:
202 print "Not creating info/access_uri - add it if you wish"
203 if created and not self.quiet:
204 print "Please edit the files in %s appropriately." % path
205
209
210 - def makeTAC(self, contents, secret=False):
211 tacfile = "buildbot.tac"
212 if os.path.exists(tacfile):
213 oldcontents = open(tacfile, "rt").read()
214 if oldcontents == contents:
215 if not self.quiet:
216 print "buildbot.tac already exists and is correct"
217 return
218 if not self.quiet:
219 print "not touching existing buildbot.tac"
220 print "creating buildbot.tac.new instead"
221 tacfile = "buildbot.tac.new"
222 f = open(tacfile, "wt")
223 f.write(contents)
224 f.close()
225 if secret:
226 os.chmod(tacfile, 0600)
227
229 target = "Makefile.sample"
230 if os.path.exists(target):
231 oldcontents = open(target, "rt").read()
232 if oldcontents == makefile_sample:
233 if not self.quiet:
234 print "Makefile.sample already exists and is correct"
235 return
236 if not self.quiet:
237 print "replacing Makefile.sample"
238 else:
239 if not self.quiet:
240 print "creating Makefile.sample"
241 f = open(target, "wt")
242 f.write(makefile_sample)
243 f.close()
244
246 target = "master.cfg.sample"
247 config_sample = open(source, "rt").read()
248 if os.path.exists(target):
249 oldcontents = open(target, "rt").read()
250 if oldcontents == config_sample:
251 if not self.quiet:
252 print "master.cfg.sample already exists and is up-to-date"
253 return
254 if not self.quiet:
255 print "replacing master.cfg.sample"
256 else:
257 if not self.quiet:
258 print "creating master.cfg.sample"
259 f = open(target, "wt")
260 f.write(config_sample)
261 f.close()
262 os.chmod(target, 0600)
263
265 webdir = os.path.join(self.basedir, "public_html")
266 if os.path.exists(webdir):
267 if not self.quiet:
268 print "public_html/ already exists: not replacing"
269 return
270 else:
271 os.mkdir(webdir)
272 if not self.quiet:
273 print "populating public_html/"
274 for target, source in files.iteritems():
275 target = os.path.join(webdir, target)
276 f = open(target, "wt")
277 f.write(open(source, "rt").read())
278 f.close()
279
291
293 new_contents = open(source, "rt").read()
294 if os.path.exists(target):
295 old_contents = open(target, "rt").read()
296 if old_contents != new_contents:
297 if overwrite:
298 if not self.quiet:
299 print "%s has old/modified contents" % target
300 print " overwriting it with new contents"
301 open(target, "wt").write(new_contents)
302 else:
303 if not self.quiet:
304 print "%s has old/modified contents" % target
305 print " writing new contents to %s.new" % target
306 open(target + ".new", "wt").write(new_contents)
307
308 else:
309 if not self.quiet:
310 print "populating %s" % target
311 open(target, "wt").write(new_contents)
312
314 if os.path.exists(source):
315 if os.path.exists(dest):
316 print "Notice: %s now overrides %s" % (dest, source)
317 print " as the latter is not used by buildbot anymore."
318 print " Decide which one you want to keep."
319 else:
320 try:
321 print "Notice: Moving %s to %s." % (source, dest)
322 print " You can (and probably want to) remove it if you haven't modified this file."
323 os.renames(source, dest)
324 except Exception, e:
325 print "Error moving %s to %s: %s" % (source, dest, str(e))
326
336
338 from buildbot.master import BuildMaster
339 from twisted.python import log, failure
340
341 master_cfg = os.path.join(self.basedir, "master.cfg")
342 if not os.path.exists(master_cfg):
343 if not self.quiet:
344 print "No master.cfg found"
345 return 1
346
347
348
349
350
351
352
353
354
355
356
357
358
359 if sys.path[0] != self.basedir:
360 sys.path.insert(0, self.basedir)
361
362 m = BuildMaster(self.basedir)
363
364
365
366
367 messages = []
368 log.addObserver(messages.append)
369 try:
370
371
372
373
374 m.loadConfig(open(master_cfg, "r"), check_synchronously_only=True)
375 except:
376 f = failure.Failure()
377 if not self.quiet:
378 print
379 for m in messages:
380 print "".join(m['message'])
381 print f
382 print
383 print "An error was detected in the master.cfg file."
384 print "Please correct the problem and run 'buildbot upgrade-master' again."
385 print
386 return 1
387 return 0
388
389 DB_HELP = """
390 The --db string is evaluated to build the DB object, which specifies
391 which database the buildmaster should use to hold scheduler state and
392 status information. The default (which creates an SQLite database in
393 BASEDIR/state.sqlite) is equivalent to:
394
395 --db='DBSpec("sqlite3", basedir+"/state.sqlite"))'
396 --db='sqlite:///state.sqlite'
397
398 To use a remote MySQL database instead, use something like:
399
400 --db='mysql://bbuser:bbpasswd@dbhost/bbdb'
401 """
402
404 optFlags = [
405 ["replace", "r", "Replace any modified files without confirmation."],
406 ]
407 optParameters = [
408 ["db", None, "sqlite:///state.sqlite",
409 "which DB to use for scheduler/status state. See below for syntax."],
410 ]
411
413 return "Usage: buildbot upgrade-master [options] [<basedir>]"
414
415 longdesc = """
416 This command takes an existing buildmaster working directory and
417 adds/modifies the files there to work with the current version of
418 buildbot. When this command is finished, the buildmaster directory should
419 look much like a brand-new one created by the 'create-master' command.
420
421 Use this after you've upgraded your buildbot installation and before you
422 restart the buildmaster to use the new version.
423
424 If you have modified the files in your working directory, this command
425 will leave them untouched, but will put the new recommended contents in a
426 .new file (for example, if index.html has been modified, this command
427 will create index.html.new). You can then look at the new version and
428 decide how to merge its contents into your modified file.
429 """+DB_HELP+"""
430 When upgrading from a pre-0.8.0 release (which did not use a database),
431 this command will create the given database and migrate data from the old
432 pickle files into it, then move the pickle files out of the way (e.g. to
433 changes.pck.old). To revert to an older release, rename the pickle files
434 back. When you are satisfied with the new version, you can delete the old
435 pickle files.
436 """
437
439 basedir = os.path.expanduser(config['basedir'])
440 m = Maker(config)
441
442
443
444 m.upgrade_public_html({
445 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"),
446 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"),
447 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"),
448 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"),
449 })
450 m.populate_if_missing(os.path.join(basedir, "master.cfg.sample"),
451 util.sibpath(__file__, "sample.cfg"),
452 overwrite=True)
453
454 m.move_if_present(os.path.join(basedir, "public_html/index.html"),
455 os.path.join(basedir, "templates/root.html"))
456
457 from buildbot.db import connector, dbspec
458 spec = dbspec.DBSpec.from_url(config["db"], basedir)
459
460
461
462 from buildbot.db.schema import manager
463 sm = manager.DBSchemaManager(spec, basedir)
464 sm.upgrade()
465
466
467 rc = m.check_master_cfg()
468 if rc:
469 return rc
470 if not config['quiet']: print "upgrade complete"
471 return 0
472
473
475 optFlags = [
476 ["force", "f",
477 "Re-use an existing directory (will not overwrite master.cfg file)"],
478 ["relocatable", "r",
479 "Create a relocatable buildbot.tac"],
480 ]
481 optParameters = [
482 ["config", "c", "master.cfg", "name of the buildmaster config file"],
483 ["log-size", "s", "1000000",
484 "size at which to rotate twisted log files"],
485 ["log-count", "l", "None",
486 "limit the number of kept old twisted log files"],
487 ["db", None, "sqlite:///state.sqlite",
488 "which DB to use for scheduler/status state. See below for syntax."],
489 ]
491 return "Usage: buildbot create-master [options] [<basedir>]"
492
493 longdesc = """
494 This command creates a buildmaster working directory and buildbot.tac file.
495 The master will live in <dir> and create various files there. If
496 --relocatable is given, then the resulting buildbot.tac file will be
497 written such that its containing directory is assumed to be the basedir.
498 This is generally a good idea.
499
500 At runtime, the master will read a configuration file (named
501 'master.cfg' by default) in its basedir. This file should contain python
502 code which eventually defines a dictionary named 'BuildmasterConfig'.
503 The elements of this dictionary are used to configure the Buildmaster.
504 See doc/config.xhtml for details about what can be controlled through
505 this interface.
506 """ + DB_HELP + """
507 The --db string is stored verbatim in the buildbot.tac file, and
508 evaluated as 'buildbot start' time to pass a DBConnector instance into
509 the newly-created BuildMaster object.
510 """
511
512 - def postOptions(self):
513 MakerBase.postOptions(self)
514 if not re.match('^\d+$', self['log-size']):
515 raise usage.UsageError("log-size parameter needs to be an int")
516 if not re.match('^\d+$', self['log-count']) and \
517 self['log-count'] != 'None':
518 raise usage.UsageError("log-count parameter needs to be an int "+
519 " or None")
520
521
522 masterTAC = """
523 import os
524
525 from twisted.application import service
526 from buildbot.master import BuildMaster
527
528 basedir = r'%(basedir)s'
529 rotateLength = %(log-size)s
530 maxRotatedFiles = %(log-count)s
531
532 # if this is a relocatable tac file, get the directory containing the TAC
533 if basedir == '.':
534 import os.path
535 basedir = os.path.abspath(os.path.dirname(__file__))
536
537 # note: this line is matched against to check that this is a buildmaster
538 # directory; do not edit it.
539 application = service.Application('buildmaster')
540 try:
541 from twisted.python.logfile import LogFile
542 from twisted.python.log import ILogObserver, FileLogObserver
543 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
544 maxRotatedFiles=maxRotatedFiles)
545 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
546 except ImportError:
547 # probably not yet twisted 8.2.0 and beyond, can't set log yet
548 pass
549
550 configfile = r'%(config)s'
551
552 m = BuildMaster(basedir, configfile)
553 m.setServiceParent(application)
554 m.log_rotation.rotateLength = rotateLength
555 m.log_rotation.maxRotatedFiles = maxRotatedFiles
556
557 """
558
560 m = Maker(config)
561 m.mkdir()
562 m.chdir()
563 if config['relocatable']:
564 config['basedir'] = '.'
565 contents = masterTAC % config
566 m.makeTAC(contents)
567 m.sampleconfig(util.sibpath(__file__, "sample.cfg"))
568 m.public_html({
569 'bg_gradient.jpg' : util.sibpath(__file__, "../status/web/files/bg_gradient.jpg"),
570 'default.css' : util.sibpath(__file__, "../status/web/files/default.css"),
571 'robots.txt' : util.sibpath(__file__, "../status/web/files/robots.txt"),
572 'favicon.ico' : util.sibpath(__file__, "../status/web/files/favicon.ico"),
573 })
574 m.makefile()
575 m.create_db()
576
577 if not m.quiet: print "buildmaster configured in %s" % m.basedir
578
579 -def stop(config, signame="TERM", wait=False):
580 import signal
581 basedir = config['basedir']
582 quiet = config['quiet']
583
584 if not isBuildmasterDir(config['basedir']):
585 print "not a buildmaster directory"
586 sys.exit(1)
587
588 os.chdir(basedir)
589 try:
590 f = open("twistd.pid", "rt")
591 except:
592 raise BuildbotNotRunningError
593 pid = int(f.read().strip())
594 signum = getattr(signal, "SIG"+signame)
595 timer = 0
596 try:
597 os.kill(pid, signum)
598 except OSError, e:
599 if e.errno != 3:
600 raise
601
602 if not wait:
603 if not quiet:
604 print "sent SIG%s to process" % signame
605 return
606 time.sleep(0.1)
607 while timer < 10:
608
609 try:
610 os.kill(pid, 0)
611 except OSError:
612 if not quiet:
613 print "buildbot process %d is dead" % pid
614 return
615 timer += 1
616 time.sleep(1)
617 if not quiet:
618 print "never saw process go away"
619
636
637
639 optFlags = [
640 ['quiet', 'q', "Don't display startup log messages"],
641 ]
643 return "Usage: buildbot start [<basedir>]"
644
647 return "Usage: buildbot stop [<basedir>]"
648
650 optFlags = [
651 ['quiet', 'q', "Don't display log messages about reconfiguration"],
652 ]
654 return "Usage: buildbot reconfig [<basedir>]"
655
656
657
659 optFlags = [
660 ['quiet', 'q', "Don't display startup log messages"],
661 ]
663 return "Usage: buildbot restart [<basedir>]"
664
666 optFlags = [
667 ['help', 'h', "Display this message"],
668 ]
669 optParameters = [
670 ["master", "m", None,
671 "Location of the buildmaster's slaveport (host:port)"],
672 ["passwd", "p", None, "Debug password to use"],
673 ]
674 buildbotOptions = [
675 [ 'debugMaster', 'passwd' ],
676 [ 'master', 'master' ],
677 ]
678
680 if len(args) > 0:
681 self['master'] = args[0]
682 if len(args) > 1:
683 self['passwd'] = args[1]
684 if len(args) > 2:
685 raise usage.UsageError("I wasn't expecting so many arguments")
686
702
704 optFlags = [
705 ['help', 'h', "Display this message"],
706 ]
707 optParameters = [
708 ["master", "m", None,
709 "Location of the buildmaster's status port (host:port)"],
710 ["username", "u", "statusClient", "Username performing the trial build"],
711 ["passwd", None, "clientpw", "password for PB authentication"],
712 ]
713 buildbotOptions = [
714 [ 'masterstatus', 'master' ],
715 ]
716
718 if len(args) > 0:
719 self['master'] = args[0]
720 if len(args) > 1:
721 raise usage.UsageError("I wasn't expecting so many arguments")
722
733
742
747
748 optParameters = [
749 ("master", "m", None,
750 "Location of the buildmaster's PBListener (host:port)"),
751 ("username", "u", None, "Username performing the commit"),
752 ("repository", "R", None, "Repository specifier"),
753 ("project", "P", None, "Project specifier"),
754 ("branch", "b", None, "Branch specifier"),
755 ("category", "c", None, "Category of repository"),
756 ("revision", "r", None, "Revision specifier"),
757 ("revision_file", None, None, "Filename containing revision spec"),
758 ("property", "p", None,
759 "A property for the change, in the format: name:value"),
760 ("comments", "m", None, "log message"),
761 ("logfile", "F", None,
762 "Read the log messages from this file (- for stdin)"),
763 ("when", "w", None, "timestamp to use as the change time"),
764 ]
765
766 buildbotOptions = [
767 [ 'master', 'master' ],
768 [ 'username', 'username' ],
769 [ 'branch', 'branch' ],
770 [ 'category', 'category' ],
771 ]
772
774 return "Usage: buildbot sendchange [options] filenames.."
778 name,value = property.split(':')
779 self['properties'][name] = value
780
781
783 """Send a single change to the buildmaster's PBChangeSource. The
784 connection will be drpoped as soon as the Change has been sent."""
785 from buildbot.clients.sendchange import Sender
786
787 user = config.get('username')
788 master = config.get('master')
789 branch = config.get('branch')
790 category = config.get('category')
791 revision = config.get('revision')
792 properties = config.get('properties', {})
793 repository = config.get('repository', '')
794 project = config.get('project', '')
795 if config.get('when'):
796 when = float(config.get('when'))
797 else:
798 when = None
799 if config.get("revision_file"):
800 revision = open(config["revision_file"],"r").read()
801
802 comments = config.get('comments')
803 if not comments and config.get('logfile'):
804 if config['logfile'] == "-":
805 f = sys.stdin
806 else:
807 f = open(config['logfile'], "rt")
808 comments = f.read()
809 if comments is None:
810 comments = ""
811
812 files = config.get('files', [])
813
814 assert user, "you must provide a username"
815 assert master, "you must provide the master location"
816
817 s = Sender(master, user)
818 d = s.send(branch, revision, comments, files, category=category, when=when,
819 properties=properties, repository=repository, project=project)
820 if runReactor:
821 d.addCallbacks(s.printSuccess, s.printFailure)
822 d.addBoth(s.stop)
823 s.run()
824 return d
825
826
828 optParameters = [
829 ["builder", None, None, "which Builder to start"],
830 ["branch", None, None, "which branch to build"],
831 ["revision", None, None, "which revision to build"],
832 ["reason", None, None, "the reason for starting the build"],
833 ]
834
836 args = list(args)
837 if len(args) > 0:
838 if self['builder'] is not None:
839 raise usage.UsageError("--builder provided in two ways")
840 self['builder'] = args.pop(0)
841 if len(args) > 0:
842 if self['reason'] is not None:
843 raise usage.UsageError("--reason provided in two ways")
844 self['reason'] = " ".join(args)
845
846
848 optParameters = [
849 ["connect", "c", None,
850 "how to reach the buildmaster, either 'ssh' or 'pb'"],
851
852 ["tryhost", None, None,
853 "the hostname (used by ssh) for the buildmaster"],
854 ["trydir", None, None,
855 "the directory (on the tryhost) where tryjobs are deposited"],
856 ["username", "u", None, "Username performing the trial build"],
857
858 ["master", "m", None,
859 "Location of the buildmaster's PBListener (host:port)"],
860 ["passwd", None, None, "password for PB authentication"],
861
862 ["diff", None, None,
863 "Filename of a patch to use instead of scanning a local tree. Use '-' for stdin."],
864 ["patchlevel", "p", 0,
865 "Number of slashes to remove from patch pathnames, like the -p option to 'patch'"],
866
867 ["baserev", None, None,
868 "Base revision to use instead of scanning a local tree."],
869
870 ["vc", None, None,
871 "The VC system in use, one of: cvs,svn,tla,baz,darcs,p4"],
872 ["branch", None, None,
873 "The branch in use, for VC systems that can't figure it out"
874 " themselves"],
875
876 ["builder", "b", None,
877 "Run the trial build on this Builder. Can be used multiple times."],
878 ["properties", None, None,
879 "A set of properties made available in the build environment, format:prop=value,propb=valueb..."],
880
881 ["try-topfile", None, None,
882 "Name of a file at the top of the tree, used to find the top. Only needed for SVN and CVS."],
883 ["try-topdir", None, None,
884 "Path to the top of the working copy. Only needed for SVN and CVS."],
885
886 ]
887
888 optFlags = [
889 ["wait", None, "wait until the builds have finished"],
890 ["dryrun", 'n', "Gather info, but don't actually submit."],
891 ["get-builder-names", None, "Get the names of available builders. Doesn't submit anything. Only supported for 'pb' connections."],
892 ]
893
894
895
896 buildbotOptions = [
897 [ 'try_connect', 'connect' ],
898
899 [ 'try_vc', 'vc' ],
900 [ 'try_branch', 'branch' ],
901 [ 'try_topdir', 'try-topdir' ],
902 [ 'try_topfile', 'try-topfile' ],
903 [ 'try_host', 'tryhost' ],
904 [ 'try_username', 'username' ],
905 [ 'try_dir', 'trydir' ],
906 [ 'try_password', 'passwd' ],
907 [ 'try_master', 'master' ],
908
909 [ 'masterstatus', 'master' ],
910 ]
911
916
918 self['builders'].append(option)
919
921
922 properties = {}
923 propertylist = option.split(",")
924 for i in range(0,len(propertylist)):
925 print propertylist[i]
926 splitproperty = propertylist[i].split("=")
927 properties[splitproperty[0]] = splitproperty[1]
928 self['properties'] = properties
929
931 self['patchlevel'] = int(option)
932
934 return "Usage: buildbot try [options]"
935
936 - def postOptions(self):
937 opts = loadOptionsFile()
938 if not self['builders']:
939 self['builders'] = opts.get('try_builders', [])
940 if opts.get('try_wait', False):
941 self['wait'] = True
942
947
949 optParameters = [
950 ["jobdir", None, None, "the jobdir (maildir) for submitting jobs"],
951 ]
952
954 import md5
955 jobdir = os.path.expanduser(config["jobdir"])
956 job = sys.stdin.read()
957
958
959
960 timestring = "%d" % time.time()
961 jobhash = md5.new(job).hexdigest()
962 fn = "%s-%s" % (timestring, jobhash)
963 tmpfile = os.path.join(jobdir, "tmp", fn)
964 newfile = os.path.join(jobdir, "new", fn)
965 f = open(tmpfile, "w")
966 f.write(job)
967 f.close()
968 os.rename(tmpfile, newfile)
969
970
972 optFlags = [
973 ['quiet', 'q', "Don't display error messages or tracebacks"],
974 ]
975
977 return "Usage :buildbot checkconfig [configFile]\n" + \
978 " If not specified, 'master.cfg' will be used as 'configFile'"
979
981 if len(args) >= 1:
982 self['configFile'] = args[0]
983 else:
984 self['configFile'] = 'master.cfg'
985
986
1005
1006
1008 synopsis = "Usage: buildbot <command> [command options]"
1009
1010 subCommands = [
1011
1012 ['create-master', None, MasterOptions,
1013 "Create and populate a directory for a new buildmaster"],
1014 ['upgrade-master', None, UpgradeMasterOptions,
1015 "Upgrade an existing buildmaster directory for the current version"],
1016 ['start', None, StartOptions, "Start a buildmaster"],
1017 ['stop', None, StopOptions, "Stop a buildmaster"],
1018 ['restart', None, RestartOptions,
1019 "Restart a buildmaster"],
1020
1021 ['reconfig', None, ReconfigOptions,
1022 "SIGHUP a buildmaster to make it re-read the config file"],
1023 ['sighup', None, ReconfigOptions,
1024 "SIGHUP a buildmaster to make it re-read the config file"],
1025
1026 ['sendchange', None, SendChangeOptions,
1027 "Send a change to the buildmaster"],
1028
1029 ['debugclient', None, DebugClientOptions,
1030 "Launch a small debug panel GUI"],
1031
1032 ['statuslog', None, StatusClientOptions,
1033 "Emit current builder status to stdout"],
1034 ['statusgui', None, StatusClientOptions,
1035 "Display a small window showing current builder status"],
1036
1037
1038 ['try', None, TryOptions, "Run a build with your local changes"],
1039
1040 ['tryserver', None, TryServerOptions,
1041 "buildmaster-side 'try' support function, not for users"],
1042
1043 ['checkconfig', None, CheckConfigOptions,
1044 "test the validity of a master.cfg config file"],
1045
1046
1047 ]
1048
1053
1055 from twisted.python import log
1056 log.startLogging(sys.stderr)
1057
1058 - def postOptions(self):
1059 if not hasattr(self, 'subOptions'):
1060 raise usage.UsageError("must specify a command")
1061
1062
1111