1
2
3 import os, sys, re, signal, shutil, types, time, tarfile, tempfile
4 from stat import ST_CTIME, ST_MTIME, ST_SIZE
5
6 from zope.interface import implements
7 from twisted.internet.protocol import ProcessProtocol
8 from twisted.internet import reactor, defer, task
9 from twisted.python import log, failure, runtime
10 from twisted.python.procutils import which
11
12 from buildbot.slave.interfaces import ISlaveCommand
13 from buildbot.slave.registry import registerSlaveCommand
14 from buildbot.util import to_text
15
16
17
18
19 command_version = "2.8"
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
49
51 """An obfuscated string in a command"""
53 self.real = real
54 self.fake = fake
55
58
61
63 rv = command
64 if type(command) == types.ListType:
65 rv = []
66 for elt in command:
67 if isinstance(elt, Obfuscated):
68 rv.append(elt.real)
69 else:
70 rv.append(to_text(elt))
71 return rv
72 get_real = staticmethod(get_real)
73
75 rv = command
76 if type(command) == types.ListType:
77 rv = []
78 for elt in command:
79 if isinstance(elt, Obfuscated):
80 rv.append(elt.fake)
81 else:
82 rv.append(to_text(elt))
83 return rv
84 get_fake = staticmethod(get_fake)
85
87 """A series of chained steps can raise this exception to indicate that
88 one of the intermediate ShellCommands has failed, such that there is no
89 point in running the remainder. 'rc' should be the non-zero exit code of
90 the failing ShellCommand."""
91
93 return "<AbandonChain rc=%s>" % self.args[0]
94
96 possibles = which(name)
97 if not possibles:
98 raise RuntimeError("Couldn't find executable for '%s'" % name)
99 return possibles[0]
100
102 """This is a replacement for shutil.rmtree that works better under
103 windows. Thanks to Bear at the OSAF for the code."""
104 if not os.path.exists(dir):
105 return
106
107 if os.path.islink(dir):
108 os.remove(dir)
109 return
110
111
112 os.chmod(dir, 0700)
113
114 for name in os.listdir(dir):
115 full_name = os.path.join(dir, name)
116
117
118 if os.name == 'nt':
119 if not os.access(full_name, os.W_OK):
120
121
122
123 os.chmod(full_name, 0600)
124
125 if os.path.isdir(full_name):
126 rmdirRecursive(full_name)
127 else:
128 os.chmod(full_name, 0700)
129 os.remove(full_name)
130 os.rmdir(dir)
131
133 debug = False
134
136 self.command = command
137 self.pending_stdin = ""
138 self.stdin_finished = False
139
141 assert not self.stdin_finished
142 if self.connected:
143 self.transport.write(data)
144 else:
145 self.pending_stdin += data
146
152
154 if self.debug:
155 log.msg("ShellCommandPP.connectionMade")
156 if not self.command.process:
157 if self.debug:
158 log.msg(" assigning self.command.process: %s" %
159 (self.transport,))
160 self.command.process = self.transport
161
162
163
164
165
166
167
168
169
170
171
172
173 if self.pending_stdin:
174 if self.debug: log.msg(" writing to stdin")
175 self.transport.write(self.pending_stdin)
176 if self.stdin_finished:
177 if self.debug: log.msg(" closing stdin")
178 self.transport.closeStdin()
179
184
189
191 if self.debug:
192 log.msg("ShellCommandPP.processEnded", status_object)
193
194
195
196 sig = status_object.value.signal
197 rc = status_object.value.exitCode
198 self.command.finished(sig, rc)
199
201 POLL_INTERVAL = 2
202
203 - def __init__(self, command, name, logfile, follow=False):
204 self.command = command
205 self.name = name
206 self.logfile = logfile
207
208 log.msg("LogFileWatcher created to watch %s" % logfile)
209
210
211
212 self.old_logfile_stats = self.statFile()
213 self.started = False
214
215
216
217 self.follow = follow
218
219
220 self.poller = task.LoopingCall(self.poll)
221
224
226 log.err(err, msg="Polling error")
227 self.poller = None
228
230 self.poll()
231 if self.poller is not None:
232 self.poller.stop()
233 if self.started:
234 self.f.close()
235
237 if os.path.exists(self.logfile):
238 s = os.stat(self.logfile)
239 return (s[ST_CTIME], s[ST_MTIME], s[ST_SIZE])
240 return None
241
243 if not self.started:
244 s = self.statFile()
245 if s == self.old_logfile_stats:
246 return
247 if not s:
248
249
250
251 self.old_logfile_stats = None
252 return
253 self.f = open(self.logfile, "rb")
254
255
256
257 if self.follow:
258 self.f.seek(s[2], 0)
259 self.started = True
260 self.f.seek(self.f.tell(), 0)
261 while True:
262 data = self.f.read(10000)
263 if not data:
264 return
265 self.command.addLogfile(self.name, data)
266
267
269
270
271
272 notreally = False
273 BACKUP_TIMEOUT = 5
274 KILL = "KILL"
275 CHUNK_LIMIT = 128*1024
276
277
278 startTime = None
279 elapsedTime = None
280
281
282
283
284
285 - def __init__(self, builder, command,
286 workdir, environ=None,
287 sendStdout=True, sendStderr=True, sendRC=True,
288 timeout=None, initialStdin=None, keepStdinOpen=False,
289 keepStdout=False, keepStderr=False, logEnviron=True,
290 logfiles={}, usePTY="slave-config"):
291 """
292
293 @param keepStdout: if True, we keep a copy of all the stdout text
294 that we've seen. This copy is available in
295 self.stdout, which can be read after the command
296 has finished.
297 @param keepStderr: same, for stderr
298
299 @param usePTY: "slave-config" -> use the SlaveBuilder's usePTY;
300 otherwise, true to use a PTY, false to not use a PTY.
301 """
302
303 self.builder = builder
304 self.command = Obfuscated.get_real(command)
305 self.fake_command = Obfuscated.get_fake(command)
306 self.sendStdout = sendStdout
307 self.sendStderr = sendStderr
308 self.sendRC = sendRC
309 self.logfiles = logfiles
310 self.workdir = workdir
311 if not os.path.exists(workdir):
312 os.makedirs(workdir)
313 self.environ = os.environ.copy()
314 if environ:
315 if environ.has_key('PYTHONPATH'):
316 ppath = environ['PYTHONPATH']
317
318
319
320 if not isinstance(ppath, str):
321
322
323 ppath = os.pathsep.join(ppath)
324
325 if self.environ.has_key('PYTHONPATH'):
326
327
328
329 ppath = ppath + os.pathsep + self.environ['PYTHONPATH']
330
331 environ['PYTHONPATH'] = ppath
332
333 self.environ.update(environ)
334 self.initialStdin = initialStdin
335 self.keepStdinOpen = keepStdinOpen
336 self.logEnviron = logEnviron
337 self.timeout = timeout
338 self.timer = None
339 self.keepStdout = keepStdout
340 self.keepStderr = keepStderr
341
342
343 if usePTY == "slave-config":
344 self.usePTY = self.builder.usePTY
345 else:
346 self.usePTY = usePTY
347
348
349
350
351
352 if runtime.platformType != "posix" or initialStdin is not None:
353 if self.usePTY and usePTY != "slave-config":
354 self.sendStatus({'header': "WARNING: disabling usePTY for this command"})
355 self.usePTY = False
356
357 self.logFileWatchers = []
358 for name,filevalue in self.logfiles.items():
359 filename = filevalue
360 follow = False
361
362
363
364 if type(filevalue) == dict:
365 filename = filevalue['filename']
366 follow = filevalue.get('follow', False)
367
368 w = LogFileWatcher(self, name,
369 os.path.join(self.workdir, filename),
370 follow=follow)
371 self.logFileWatchers.append(w)
372
374 return "<slavecommand.ShellCommand '%s'>" % self.fake_command
375
378
380
381
382 if self.keepStdout:
383 self.stdout = ""
384 if self.keepStderr:
385 self.stderr = ""
386 self.deferred = defer.Deferred()
387 try:
388 self._startCommand()
389 except:
390 log.msg("error in ShellCommand._startCommand")
391 log.err()
392
393 self.deferred.errback(AbandonChain(-1))
394 return self.deferred
395
397
398 if not os.path.isdir(self.workdir):
399 os.makedirs(self.workdir)
400 log.msg("ShellCommand._startCommand")
401 if self.notreally:
402 self.sendStatus({'header': "command '%s' in dir %s" % \
403 (self.fake_command, self.workdir)})
404 self.sendStatus({'header': "(not really)\n"})
405 self.finished(None, 0)
406 return
407
408 self.pp = ShellCommandPP(self)
409
410 if type(self.command) in types.StringTypes:
411 if runtime.platformType == 'win32':
412 argv = os.environ['COMSPEC'].split()
413 if '/c' not in argv: argv += ['/c']
414 argv += [self.command]
415 else:
416
417
418 argv = ['/bin/sh', '-c', self.command]
419 display = self.fake_command
420 else:
421 if runtime.platformType == 'win32' and not self.command[0].lower().endswith(".exe"):
422 argv = os.environ['COMSPEC'].split()
423 if '/c' not in argv: argv += ['/c']
424 argv += list(self.command)
425 else:
426 argv = self.command
427 display = " ".join(self.fake_command)
428
429
430
431
432 if not self.environ.get('MACHTYPE', None) == 'i686-pc-msys':
433 self.environ['PWD'] = os.path.abspath(self.workdir)
434
435
436
437
438
439
440
441 log.msg(" " + display)
442 self.sendStatus({'header': display+"\n"})
443
444
445 msg = " in dir %s" % (self.workdir,)
446 if self.timeout:
447 msg += " (timeout %d secs)" % (self.timeout,)
448 log.msg(" " + msg)
449 self.sendStatus({'header': msg+"\n"})
450
451 msg = " watching logfiles %s" % (self.logfiles,)
452 log.msg(" " + msg)
453 self.sendStatus({'header': msg+"\n"})
454
455
456 msg = " argv: %s" % (self.fake_command,)
457 log.msg(" " + msg)
458 self.sendStatus({'header': msg+"\n"})
459
460
461 if self.logEnviron:
462 msg = " environment:\n"
463 env_names = self.environ.keys()
464 env_names.sort()
465 for name in env_names:
466 msg += " %s=%s\n" % (name, self.environ[name])
467 log.msg(" environment: %s" % (self.environ,))
468 self.sendStatus({'header': msg})
469
470 if self.initialStdin:
471 msg = " writing %d bytes to stdin" % len(self.initialStdin)
472 log.msg(" " + msg)
473 self.sendStatus({'header': msg+"\n"})
474
475 if self.keepStdinOpen:
476 msg = " leaving stdin open"
477 else:
478 msg = " closing stdin"
479 log.msg(" " + msg)
480 self.sendStatus({'header': msg+"\n"})
481
482 msg = " using PTY: %s" % bool(self.usePTY)
483 log.msg(" " + msg)
484 self.sendStatus({'header': msg+"\n"})
485
486
487 if self.initialStdin:
488 self.pp.writeStdin(self.initialStdin)
489 if not self.keepStdinOpen:
490 self.pp.closeStdin()
491
492
493
494
495
496
497
498
499
500
501 self.process = None
502 self.startTime = time.time()
503
504 p = reactor.spawnProcess(self.pp, argv[0], argv,
505 self.environ,
506 self.workdir,
507 usePTY=self.usePTY)
508
509 if not self.process:
510 self.process = p
511
512
513
514
515
516
517
518 if self.timeout:
519 self.timer = reactor.callLater(self.timeout, self.doTimeout)
520
521 for w in self.logFileWatchers:
522 w.start()
523
524
526
527
528 LIMIT = self.CHUNK_LIMIT
529 for i in range(0, len(data), LIMIT):
530 yield data[i:i+LIMIT]
531
533 if self.sendStdout:
534 for chunk in self._chunkForSend(data):
535 self.sendStatus({'stdout': chunk})
536 if self.keepStdout:
537 self.stdout += data
538 if self.timer:
539 self.timer.reset(self.timeout)
540
542 if self.sendStderr:
543 for chunk in self._chunkForSend(data):
544 self.sendStatus({'stderr': chunk})
545 if self.keepStderr:
546 self.stderr += data
547 if self.timer:
548 self.timer.reset(self.timeout)
549
555
557 self.elapsedTime = time.time() - self.startTime
558 log.msg("command finished with signal %s, exit code %s, elapsedTime: %0.6f" % (sig,rc,self.elapsedTime))
559 for w in self.logFileWatchers:
560
561 w.stop()
562 if sig is not None:
563 rc = -1
564 if self.sendRC:
565 if sig is not None:
566 self.sendStatus(
567 {'header': "process killed by signal %d\n" % sig})
568 self.sendStatus({'rc': rc})
569 self.sendStatus({'header': "elapsedTime=%0.6f\n" % self.elapsedTime})
570 if self.timer:
571 self.timer.cancel()
572 self.timer = None
573 d = self.deferred
574 self.deferred = None
575 if d:
576 d.callback(rc)
577 else:
578 log.msg("Hey, command %s finished twice" % self)
579
581 log.msg("ShellCommand.failed: command failed: %s" % (why,))
582 if self.timer:
583 self.timer.cancel()
584 self.timer = None
585 d = self.deferred
586 self.deferred = None
587 if d:
588 d.errback(why)
589 else:
590 log.msg("Hey, command %s finished twice" % self)
591
593 self.timer = None
594 msg = "command timed out: %d seconds without output" % self.timeout
595 self.kill(msg)
596
597 - def kill(self, msg):
598
599
600 if self.timer:
601 self.timer.cancel()
602 self.timer = None
603 if hasattr(self.process, "pid") and self.process.pid is not None:
604 msg += ", killing pid %s" % self.process.pid
605 log.msg(msg)
606 self.sendStatus({'header': "\n" + msg + "\n"})
607
608 hit = 0
609 if runtime.platformType == "posix":
610 try:
611
612
613
614
615
616
617
618
619
620 sig = None
621 if self.KILL is not None:
622 sig = getattr(signal, "SIG"+ self.KILL, None)
623
624 if self.KILL == None:
625 log.msg("self.KILL==None, only pretending to kill child")
626 elif sig is None:
627 log.msg("signal module is missing SIG%s" % self.KILL)
628 elif not hasattr(os, "kill"):
629 log.msg("os module is missing the 'kill' function")
630 elif not hasattr(self.process, "pid") or self.process.pid is None:
631 log.msg("self.process has no pid")
632 else:
633 log.msg("trying os.kill(-pid, %d)" % (sig,))
634
635 os.kill(-self.process.pid, sig)
636 log.msg(" signal %s sent successfully" % sig)
637 hit = 1
638 except OSError:
639
640
641 pass
642 if not hit:
643 try:
644 if self.KILL is None:
645 log.msg("self.KILL==None, only pretending to kill child")
646 else:
647 log.msg("trying process.signalProcess('KILL')")
648 self.process.signalProcess(self.KILL)
649 log.msg(" signal %s sent successfully" % (self.KILL,))
650 hit = 1
651 except OSError:
652
653 pass
654 if not hit:
655 log.msg("signalProcess/os.kill failed both times")
656
657 if runtime.platformType == "posix":
658
659
660
661 self.pp.transport.loseConnection()
662
663
664
665 self.timer = reactor.callLater(self.BACKUP_TIMEOUT,
666 self.doBackupTimeout)
667
669 log.msg("we tried to kill the process, and it wouldn't die.."
670 " finish anyway")
671 self.timer = None
672 self.sendStatus({'header': "SIGKILL failed to kill process\n"})
673 if self.sendRC:
674 self.sendStatus({'header': "using fake rc=-1\n"})
675 self.sendStatus({'rc': -1})
676 self.failed(TimeoutError("SIGKILL failed to kill process"))
677
678
681
684
685
687 implements(ISlaveCommand)
688
689 """This class defines one command that can be invoked by the build master.
690 The command is executed on the slave side, and always sends back a
691 completion message when it finishes. It may also send intermediate status
692 as it runs (by calling builder.sendStatus). Some commands can be
693 interrupted (either by the build master or a local timeout), in which
694 case the step is expected to complete normally with a status message that
695 indicates an error occurred.
696
697 These commands are used by BuildSteps on the master side. Each kind of
698 BuildStep uses a single Command. The slave must implement all the
699 Commands required by the set of BuildSteps used for any given build:
700 this is checked at startup time.
701
702 All Commands are constructed with the same signature:
703 c = CommandClass(builder, args)
704 where 'builder' is the parent SlaveBuilder object, and 'args' is a
705 dict that is interpreted per-command.
706
707 The setup(args) method is available for setup, and is run from __init__.
708
709 The Command is started with start(). This method must be implemented in a
710 subclass, and it should return a Deferred. When your step is done, you
711 should fire the Deferred (the results are not used). If the command is
712 interrupted, it should fire the Deferred anyway.
713
714 While the command runs. it may send status messages back to the
715 buildmaster by calling self.sendStatus(statusdict). The statusdict is
716 interpreted by the master-side BuildStep however it likes.
717
718 A separate completion message is sent when the deferred fires, which
719 indicates that the Command has finished, but does not carry any status
720 data. If the Command needs to return an exit code of some sort, that
721 should be sent as a regular status message before the deferred is fired .
722 Once builder.commandComplete has been run, no more status messages may be
723 sent.
724
725 If interrupt() is called, the Command should attempt to shut down as
726 quickly as possible. Child processes should be killed, new ones should
727 not be started. The Command should send some kind of error status update,
728 then complete as usual by firing the Deferred.
729
730 .interrupted should be set by interrupt(), and can be tested to avoid
731 sending multiple error status messages.
732
733 If .running is False, the bot is shutting down (or has otherwise lost the
734 connection to the master), and should not send any status messages. This
735 is checked in Command.sendStatus .
736
737 """
738
739
740
741
742
743 debug = False
744 interrupted = False
745 running = False
746
747
748 - def __init__(self, builder, stepId, args):
753
755 """Override this in a subclass to extract items from the args dict."""
756 pass
757
763
765 """Start the command. This method should return a Deferred that will
766 fire when the command has completed. The Deferred's argument will be
767 ignored.
768
769 This method should be overridden by subclasses."""
770 raise NotImplementedError, "You must implement this in a subclass"
771
780
784
786 """Override this in a subclass to allow commands to be interrupted.
787 May be called multiple times, test and set self.interrupted=True if
788 this matters."""
789 pass
790
792 self.running = False
793 return res
794
795
796
798 if type(rc) is not int:
799 log.msg("weird, _abandonOnFailure was given rc=%s (%s)" % \
800 (rc, type(rc)))
801 assert isinstance(rc, int)
802 if rc != 0:
803 raise AbandonChain(rc)
804 return rc
805
808
810 log.msg("_checkAbandoned", why)
811 why.trap(AbandonChain)
812 log.msg(" abandoning chain", why.value)
813 self.sendStatus({'rc': why.value.args[0]})
814 return None
815
816
817
819 """
820 Upload a file from slave to build master
821 Arguments:
822
823 - ['workdir']: base directory to use
824 - ['slavesrc']: name of the slave-side file to read from
825 - ['writer']: RemoteReference to a transfer._FileWriter object
826 - ['maxsize']: max size (in bytes) of file to write
827 - ['blocksize']: max size for each data block
828 """
829 debug = False
830
832 self.workdir = args['workdir']
833 self.filename = args['slavesrc']
834 self.writer = args['writer']
835 self.remaining = args['maxsize']
836 self.blocksize = args['blocksize']
837 self.stderr = None
838 self.rc = 0
839
841 if self.debug:
842 log.msg('SlaveFileUploadCommand started')
843
844
845 self.path = os.path.join(self.builder.basedir,
846 self.workdir,
847 os.path.expanduser(self.filename))
848 try:
849 self.fp = open(self.path, 'rb')
850 if self.debug:
851 log.msg('Opened %r for upload' % self.path)
852 except:
853
854 self.fp = None
855 self.stderr = 'Cannot open file %r for upload' % self.path
856 self.rc = 1
857 if self.debug:
858 log.msg('Cannot open file %r for upload' % self.path)
859
860 self.sendStatus({'header': "sending %s" % self.path})
861
862 d = defer.Deferred()
863 reactor.callLater(0, self._loop, d)
864 def _close(res):
865
866 d1 = self.writer.callRemote("close")
867 d1.addErrback(log.err)
868 d1.addCallback(lambda ignored: res)
869 return d1
870 d.addBoth(_close)
871 d.addBoth(self.finished)
872 return d
873
874 - def _loop(self, fire_when_done):
875 d = defer.maybeDeferred(self._writeBlock)
876 def _done(finished):
877 if finished:
878 fire_when_done.callback(None)
879 else:
880 self._loop(fire_when_done)
881 def _err(why):
882 fire_when_done.errback(why)
883 d.addCallbacks(_done, _err)
884 return None
885
887 """Write a block of data to the remote writer"""
888
889 if self.interrupted or self.fp is None:
890 if self.debug:
891 log.msg('SlaveFileUploadCommand._writeBlock(): end')
892 return True
893
894 length = self.blocksize
895 if self.remaining is not None and length > self.remaining:
896 length = self.remaining
897
898 if length <= 0:
899 if self.stderr is None:
900 self.stderr = 'Maximum filesize reached, truncating file %r' \
901 % self.path
902 self.rc = 1
903 data = ''
904 else:
905 data = self.fp.read(length)
906
907 if self.debug:
908 log.msg('SlaveFileUploadCommand._writeBlock(): '+
909 'allowed=%d readlen=%d' % (length, len(data)))
910 if len(data) == 0:
911 log.msg("EOF: callRemote(close)")
912 return True
913
914 if self.remaining is not None:
915 self.remaining = self.remaining - len(data)
916 assert self.remaining >= 0
917 d = self.writer.callRemote('write', data)
918 d.addCallback(lambda res: False)
919 return d
920
922 if self.debug:
923 log.msg('interrupted')
924 if self.interrupted:
925 return
926 if self.stderr is None:
927 self.stderr = 'Upload of %r interrupted' % self.path
928 self.rc = 1
929 self.interrupted = True
930
931
933 if self.debug:
934 log.msg('finished: stderr=%r, rc=%r' % (self.stderr, self.rc))
935 if self.stderr is None:
936 self.sendStatus({'rc': self.rc})
937 else:
938 self.sendStatus({'stderr': self.stderr, 'rc': self.rc})
939 return res
940
941 registerSlaveCommand("uploadFile", SlaveFileUploadCommand, command_version)
942
943
945 """
946 Upload a directory from slave to build master
947 Arguments:
948
949 - ['workdir']: base directory to use
950 - ['slavesrc']: name of the slave-side directory to read from
951 - ['writer']: RemoteReference to a transfer._DirectoryWriter object
952 - ['maxsize']: max size (in bytes) of file to write
953 - ['blocksize']: max size for each data block
954 - ['compress']: one of [None, 'bz2', 'gz']
955 """
956 debug = True
957
959 self.workdir = args['workdir']
960 self.dirname = args['slavesrc']
961 self.writer = args['writer']
962 self.remaining = args['maxsize']
963 self.blocksize = args['blocksize']
964 self.compress = args['compress']
965 self.stderr = None
966 self.rc = 0
967
969 if self.debug:
970 log.msg('SlaveDirectoryUploadCommand started')
971
972 self.path = os.path.join(self.builder.basedir,
973 self.workdir,
974 os.path.expanduser(self.dirname))
975 if self.debug:
976 log.msg("path: %r" % self.path)
977
978
979 fd, self.tarname = tempfile.mkstemp()
980 fileobj = os.fdopen(fd, 'w')
981 if self.compress == 'bz2':
982 mode='w|bz2'
983 elif self.compress == 'gz':
984 mode='w|gz'
985 else:
986 mode = 'w'
987 archive = tarfile.open(name=self.tarname, mode=mode, fileobj=fileobj)
988 archive.add(self.path, '')
989 archive.close()
990 fileobj.close()
991
992
993 self.fp = open(self.tarname, 'rb')
994
995 self.sendStatus({'header': "sending %s" % self.path})
996
997 d = defer.Deferred()
998 reactor.callLater(0, self._loop, d)
999 def unpack(res):
1000
1001 d1 = self.writer.callRemote("unpack")
1002 d1.addErrback(log.err)
1003 d1.addCallback(lambda ignored: res)
1004 return d1
1005 d.addCallback(unpack)
1006 d.addBoth(self.finished)
1007 return d
1008
1010 self.fp.close()
1011 os.remove(self.tarname)
1012 if self.debug:
1013 log.msg('finished: stderr=%r, rc=%r' % (self.stderr, self.rc))
1014 if self.stderr is None:
1015 self.sendStatus({'rc': self.rc})
1016 else:
1017 self.sendStatus({'stderr': self.stderr, 'rc': self.rc})
1018 return res
1019
1020 registerSlaveCommand("uploadDirectory", SlaveDirectoryUploadCommand, command_version)
1021
1022
1024 """
1025 Download a file from master to slave
1026 Arguments:
1027
1028 - ['workdir']: base directory to use
1029 - ['slavedest']: name of the slave-side file to be created
1030 - ['reader']: RemoteReference to a transfer._FileReader object
1031 - ['maxsize']: max size (in bytes) of file to write
1032 - ['blocksize']: max size for each data block
1033 - ['mode']: access mode for the new file
1034 """
1035 debug = False
1036
1038 self.workdir = args['workdir']
1039 self.filename = args['slavedest']
1040 self.reader = args['reader']
1041 self.bytes_remaining = args['maxsize']
1042 self.blocksize = args['blocksize']
1043 self.mode = args['mode']
1044 self.stderr = None
1045 self.rc = 0
1046
1048 if self.debug:
1049 log.msg('SlaveFileDownloadCommand starting')
1050
1051
1052 self.path = os.path.join(self.builder.basedir,
1053 self.workdir,
1054 os.path.expanduser(self.filename))
1055
1056 dirname = os.path.dirname(self.path)
1057 if not os.path.exists(dirname):
1058 os.makedirs(dirname)
1059
1060 try:
1061 self.fp = open(self.path, 'wb')
1062 if self.debug:
1063 log.msg('Opened %r for download' % self.path)
1064 if self.mode is not None:
1065
1066
1067
1068
1069
1070
1071
1072 os.chmod(self.path, self.mode)
1073 except IOError:
1074
1075 self.fp = None
1076 self.stderr = 'Cannot open file %r for download' % self.path
1077 self.rc = 1
1078 if self.debug:
1079 log.msg('Cannot open file %r for download' % self.path)
1080
1081 d = defer.Deferred()
1082 reactor.callLater(0, self._loop, d)
1083 def _close(res):
1084
1085 d1 = self.reader.callRemote('close')
1086 d1.addErrback(log.err)
1087 d1.addCallback(lambda ignored: res)
1088 return d1
1089 d.addBoth(_close)
1090 d.addBoth(self.finished)
1091 return d
1092
1093 - def _loop(self, fire_when_done):
1094 d = defer.maybeDeferred(self._readBlock)
1095 def _done(finished):
1096 if finished:
1097 fire_when_done.callback(None)
1098 else:
1099 self._loop(fire_when_done)
1100 def _err(why):
1101 fire_when_done.errback(why)
1102 d.addCallbacks(_done, _err)
1103 return None
1104
1106 """Read a block of data from the remote reader."""
1107
1108 if self.interrupted or self.fp is None:
1109 if self.debug:
1110 log.msg('SlaveFileDownloadCommand._readBlock(): end')
1111 return True
1112
1113 length = self.blocksize
1114 if self.bytes_remaining is not None and length > self.bytes_remaining:
1115 length = self.bytes_remaining
1116
1117 if length <= 0:
1118 if self.stderr is None:
1119 self.stderr = 'Maximum filesize reached, truncating file %r' \
1120 % self.path
1121 self.rc = 1
1122 return True
1123 else:
1124 d = self.reader.callRemote('read', length)
1125 d.addCallback(self._writeData)
1126 return d
1127
1129 if self.debug:
1130 log.msg('SlaveFileDownloadCommand._readBlock(): readlen=%d' %
1131 len(data))
1132 if len(data) == 0:
1133 return True
1134
1135 if self.bytes_remaining is not None:
1136 self.bytes_remaining = self.bytes_remaining - len(data)
1137 assert self.bytes_remaining >= 0
1138 self.fp.write(data)
1139 return False
1140
1142 if self.debug:
1143 log.msg('interrupted')
1144 if self.interrupted:
1145 return
1146 if self.stderr is None:
1147 self.stderr = 'Download of %r interrupted' % self.path
1148 self.rc = 1
1149 self.interrupted = True
1150
1151
1152
1154 if self.fp is not None:
1155 self.fp.close()
1156
1157 if self.debug:
1158 log.msg('finished: stderr=%r, rc=%r' % (self.stderr, self.rc))
1159 if self.stderr is None:
1160 self.sendStatus({'rc': self.rc})
1161 else:
1162 self.sendStatus({'stderr': self.stderr, 'rc': self.rc})
1163 return res
1164
1165 registerSlaveCommand("downloadFile", SlaveFileDownloadCommand, command_version)
1166
1167
1168
1170 """This is a Command which runs a shell command. The args dict contains
1171 the following keys:
1172
1173 - ['command'] (required): a shell command to run. If this is a string,
1174 it will be run with /bin/sh (['/bin/sh',
1175 '-c', command]). If it is a list
1176 (preferred), it will be used directly.
1177 - ['workdir'] (required): subdirectory in which the command will be
1178 run, relative to the builder dir
1179 - ['env']: a dict of environment variables to augment/replace
1180 os.environ . PYTHONPATH is treated specially, and
1181 should be a list of path components to be prepended to
1182 any existing PYTHONPATH environment variable.
1183 - ['initial_stdin']: a string which will be written to the command's
1184 stdin as soon as it starts
1185 - ['keep_stdin_open']: unless True, the command's stdin will be
1186 closed as soon as initial_stdin has been
1187 written. Set this to True if you plan to write
1188 to stdin after the command has been started.
1189 - ['want_stdout']: 0 if stdout should be thrown away
1190 - ['want_stderr']: 0 if stderr should be thrown away
1191 - ['usePTY']: True or False if the command should use a PTY (defaults to
1192 configuration of the slave)
1193 - ['not_really']: 1 to skip execution and return rc=0
1194 - ['timeout']: seconds of silence to tolerate before killing command
1195 - ['logfiles']: dict mapping LogFile name to the workdir-relative
1196 filename of a local log file. This local file will be
1197 watched just like 'tail -f', and all changes will be
1198 written to 'log' status updates.
1199
1200 ShellCommand creates the following status messages:
1201 - {'stdout': data} : when stdout data is available
1202 - {'stderr': data} : when stderr data is available
1203 - {'header': data} : when headers (command start/stop) are available
1204 - {'log': (logfile_name, data)} : when log files have new contents
1205 - {'rc': rc} : when the process has terminated
1206 """
1207
1209 args = self.args
1210
1211 assert args['workdir'] is not None
1212 workdir = os.path.join(self.builder.basedir, args['workdir'])
1213
1214 c = ShellCommand(self.builder, args['command'],
1215 workdir, environ=args.get('env'),
1216 timeout=args.get('timeout', None),
1217 sendStdout=args.get('want_stdout', True),
1218 sendStderr=args.get('want_stderr', True),
1219 sendRC=True,
1220 initialStdin=args.get('initial_stdin'),
1221 keepStdinOpen=args.get('keep_stdin_open'),
1222 logfiles=args.get('logfiles', {}),
1223 usePTY=args.get('usePTY', "slave-config"),
1224 )
1225 self.command = c
1226 d = self.command.start()
1227 return d
1228
1232
1235
1238
1239 registerSlaveCommand("shell", SlaveShellCommand, command_version)
1240
1241
1243 """
1244 I am a dummy no-op command that by default takes 5 seconds to complete.
1245 See L{buildbot.steps.dummy.RemoteDummy}
1246 """
1247
1249 self.d = defer.Deferred()
1250 log.msg(" starting dummy command [%s]" % self.stepId)
1251 self.timer = reactor.callLater(1, self.doStatus)
1252 return self.d
1253
1261
1267
1269 log.msg(" dummy command finished [%s]" % self.stepId)
1270 if self.interrupted:
1271 self.sendStatus({'rc': 1})
1272 else:
1273 self.sendStatus({'rc': 0})
1274 self.d.callback(0)
1275
1276 registerSlaveCommand("dummy", DummyCommand, command_version)
1277
1278
1279
1280
1281
1282 waitCommandRegistry = {}
1283
1285 """
1286 I am a dummy command used by the buildbot unit test suite. I want for the
1287 unit test to tell us to finish. See L{buildbot.steps.dummy.Wait}
1288 """
1289
1291 self.d = defer.Deferred()
1292 log.msg(" starting wait command [%s]" % self.stepId)
1293 handle = self.args['handle']
1294 cb = waitCommandRegistry[handle]
1295 del waitCommandRegistry[handle]
1296 def _called():
1297 log.msg(" wait-%s starting" % (handle,))
1298 d = cb()
1299 def _done(res):
1300 log.msg(" wait-%s finishing: %s" % (handle, res))
1301 return res
1302 d.addBoth(_done)
1303 d.addCallbacks(self.finished, self.failed)
1304 reactor.callLater(0, _called)
1305 return self.d
1306
1313
1315 log.msg(" wait command finished [%s]" % self.stepId)
1316 if self.interrupted:
1317 self.sendStatus({'rc': 2})
1318 else:
1319 self.sendStatus({'rc': 0})
1320 self.d.callback(0)
1322 log.msg(" wait command failed [%s]" % self.stepId)
1323 self.sendStatus({'rc': 1})
1324 self.d.callback(0)
1325
1326 registerSlaveCommand("dummy.wait", WaitCommand, command_version)
1327
1328
1330 """Abstract base class for Version Control System operations (checkout
1331 and update). This class extracts the following arguments from the
1332 dictionary received from the master:
1333
1334 - ['workdir']: (required) the subdirectory where the buildable sources
1335 should be placed
1336
1337 - ['mode']: one of update/copy/clobber/export, defaults to 'update'
1338
1339 - ['revision']: If not None, this is an int or string which indicates
1340 which sources (along a time-like axis) should be used.
1341 It is the thing you provide as the CVS -r or -D
1342 argument.
1343
1344 - ['patch']: If not None, this is a tuple of (striplevel, patch)
1345 which contains a patch that should be applied after the
1346 checkout has occurred. Once applied, the tree is no
1347 longer eligible for use with mode='update', and it only
1348 makes sense to use this in conjunction with a
1349 ['revision'] argument. striplevel is an int, and patch
1350 is a string in standard unified diff format. The patch
1351 will be applied with 'patch -p%d <PATCH', with
1352 STRIPLEVEL substituted as %d. The command will fail if
1353 the patch process fails (rejected hunks).
1354
1355 - ['timeout']: seconds of silence tolerated before we kill off the
1356 command
1357
1358 - ['retry']: If not None, this is a tuple of (delay, repeats)
1359 which means that any failed VC updates should be
1360 reattempted, up to REPEATS times, after a delay of
1361 DELAY seconds. This is intended to deal with slaves
1362 that experience transient network failures.
1363 """
1364
1365 sourcedata = ""
1366
1368
1369
1370
1371 self.env = os.environ.copy()
1372 self.env['LC_MESSAGES'] = "C"
1373
1374 self.workdir = args['workdir']
1375 self.mode = args.get('mode', "update")
1376 self.revision = args.get('revision')
1377 self.patch = args.get('patch')
1378 self.timeout = args.get('timeout', 120)
1379 self.retry = args.get('retry')
1380
1381
1382
1412
1414
1415 if self.mode in ("copy", "clobber", "export"):
1416 d.addCallback(self.doClobber, self.workdir)
1417
1422
1423 - def doVC(self, res):
1436
1438 try:
1439 olddata = open(self.sourcedatafile, "r").read()
1440 if olddata != self.sourcedata:
1441 return False
1442 except IOError:
1443 return False
1444 return True
1445
1447 d = defer.maybeDeferred(self.parseGotRevision)
1448 d.addCallback(lambda got_revision:
1449 self.sendStatus({'got_revision': got_revision}))
1450 return d
1451
1453 """Override this in a subclass. It should return a string that
1454 represents which revision was actually checked out, or a Deferred
1455 that will fire with such a string. If, in a future build, you were to
1456 pass this 'got_revision' string in as the 'revision' component of a
1457 SourceStamp, you should wind up with the same source code as this
1458 checkout just obtained.
1459
1460 It is probably most useful to scan self.command.stdout for a string
1461 of some sort. Be sure to set keepStdout=True on the VC command that
1462 you run, so that you'll have something available to look at.
1463
1464 If this information is unavailable, just return None."""
1465
1466 return None
1467
1469 open(self.sourcedatafile, "w").write(self.sourcedata)
1470 return res
1471
1473 raise NotImplementedError("this must be implemented in a subclass")
1474
1476 raise NotImplementedError("this must be implemented in a subclass")
1477
1479 raise NotImplementedError("this must be implemented in a subclass")
1480
1492
1501
1503 """We get here somewhere after a VC chain has finished. res could
1504 be::
1505
1506 - 0: the operation was successful
1507 - nonzero: the operation failed. retry if possible
1508 - AbandonChain: the operation failed, someone else noticed. retry.
1509 - Failure: some other exception, re-raise
1510 """
1511
1512 if isinstance(res, failure.Failure):
1513 if self.interrupted:
1514 return res
1515 res.trap(AbandonChain)
1516 else:
1517 if type(res) is int and res == 0:
1518 return res
1519 if self.interrupted:
1520 raise AbandonChain(1)
1521
1522 if self.retry:
1523 delay, repeats = self.retry
1524 if repeats >= 0:
1525 self.retry = (delay, repeats-1)
1526 msg = ("update failed, trying %d more times after %d seconds"
1527 % (repeats, delay))
1528 self.sendStatus({'header': msg + "\n"})
1529 log.msg(msg)
1530 d = defer.Deferred()
1531 self.maybeClobber(d)
1532 d.addCallback(lambda res: self.doVCFull())
1533 d.addBoth(self.maybeDoVCRetry)
1534 reactor.callLater(delay, d.callback, None)
1535 return d
1536 return res
1537
1574
1576
1577 fromdir = os.path.join(self.builder.basedir, self.srcdir)
1578 todir = os.path.join(self.builder.basedir, self.workdir)
1579 if runtime.platformType != "posix":
1580 self.sendStatus({'header': "Since we're on a non-POSIX platform, "
1581 "we're not going to try to execute cp in a subprocess, but instead "
1582 "use shutil.copytree(), which will block until it is complete. "
1583 "fromdir: %s, todir: %s\n" % (fromdir, todir)})
1584 shutil.copytree(fromdir, todir)
1585 return defer.succeed(0)
1586
1587 if not os.path.exists(os.path.dirname(todir)):
1588 os.makedirs(os.path.dirname(todir))
1589 if os.path.exists(todir):
1590
1591 log.msg("cp target '%s' already exists -- cp will not do what you think!" % todir)
1592
1593 command = ['cp', '-R', '-P', '-p', fromdir, todir]
1594 c = ShellCommand(self.builder, command, self.builder.basedir,
1595 sendRC=False, timeout=self.timeout, usePTY=False)
1596 self.command = c
1597 d = c.start()
1598 d.addCallback(self._abandonOnFailure)
1599 return d
1600
1602 patchlevel, diff = self.patch
1603 command = [getCommand("patch"), '-p%d' % patchlevel]
1604 dir = os.path.join(self.builder.basedir, self.workdir)
1605
1606 open(os.path.join(dir, ".buildbot-patched"), "w").write("patched\n")
1607
1608 c = ShellCommand(self.builder, command, dir,
1609 sendRC=False, timeout=self.timeout,
1610 initialStdin=diff, usePTY=False)
1611 self.command = c
1612 d = c.start()
1613 d.addCallback(self._abandonOnFailure)
1614 return d
1615
1616
1617 -class CVS(SourceBase):
1618 """CVS-specific VC operation. In addition to the arguments handled by
1619 SourceBase, this command reads the following keys:
1620
1621 ['cvsroot'] (required): the CVSROOT repository string
1622 ['cvsmodule'] (required): the module to be retrieved
1623 ['branch']: a '-r' tag or branch name to use for the checkout/update
1624 ['login']: a string for use as a password to 'cvs login'
1625 ['global_options']: a list of strings to use before the CVS verb
1626 ['checkout_options']: a list of strings to use after checkout,
1627 but before revision and branch specifiers
1628 """
1629
1630 header = "cvs operation"
1631
1633 SourceBase.setup(self, args)
1634 self.vcexe = getCommand("cvs")
1635 self.cvsroot = args['cvsroot']
1636 self.cvsmodule = args['cvsmodule']
1637 self.global_options = args.get('global_options', [])
1638 self.checkout_options = args.get('checkout_options', [])
1639 self.branch = args.get('branch')
1640 self.login = args.get('login')
1641 self.sourcedata = "%s\n%s\n%s\n" % (self.cvsroot, self.cvsmodule,
1642 self.branch)
1643
1645 if os.path.exists(os.path.join(self.builder.basedir,
1646 self.srcdir, ".buildbot-patched")):
1647 return False
1648 return os.path.isdir(os.path.join(self.builder.basedir,
1649 self.srcdir, "CVS"))
1650
1667
1671
1673 d = os.path.join(self.builder.basedir, self.srcdir)
1674 command = [self.vcexe, '-z3'] + self.global_options + ['update', '-dP']
1675 if self.branch:
1676 command += ['-r', self.branch]
1677 if self.revision:
1678 command += ['-D', self.revision]
1679 c = ShellCommand(self.builder, command, d,
1680 sendRC=False, timeout=self.timeout, usePTY=False)
1681 self.command = c
1682 return c.start()
1683
1685 d = self.builder.basedir
1686 if self.mode == "export":
1687 verb = "export"
1688 else:
1689 verb = "checkout"
1690 command = ([self.vcexe, '-d', self.cvsroot, '-z3'] +
1691 self.global_options +
1692 [verb, '-d', self.srcdir])
1693
1694 if verb == "checkout":
1695 command += self.checkout_options
1696 if self.branch:
1697 command += ['-r', self.branch]
1698 if self.revision:
1699 command += ['-D', self.revision]
1700 command += [self.cvsmodule]
1701
1702 c = ShellCommand(self.builder, command, d,
1703 sendRC=False, timeout=self.timeout, usePTY=False)
1704 self.command = c
1705 return c.start()
1706
1708
1709
1710
1711
1712 return time.strftime("%Y-%m-%d %H:%M:%S +0000", time.gmtime())
1713
1714 registerSlaveCommand("cvs", CVS, command_version)
1715
1716 -class SVN(SourceBase):
1717 """Subversion-specific VC operation. In addition to the arguments
1718 handled by SourceBase, this command reads the following keys:
1719
1720 ['svnurl'] (required): the SVN repository string
1721 ['username'] Username passed to the svn command
1722 ['password'] Password passed to the svn command
1723 """
1724
1725 header = "svn operation"
1726
1728 SourceBase.setup(self, args)
1729 self.vcexe = getCommand("svn")
1730 self.svnurl = args['svnurl']
1731 self.sourcedata = "%s\n" % self.svnurl
1732
1733 self.svn_args = []
1734 if args.has_key('username'):
1735 self.svn_args.extend(["--username", args['username']])
1736 if args.has_key('password'):
1737 self.svn_args.extend(["--password", Obfuscated(args['password'], "XXXX")])
1738 if args.get('extra_args', None) is not None:
1739 self.svn_args.extend(args['extra_args'])
1740
1742 if os.path.exists(os.path.join(self.builder.basedir,
1743 self.srcdir, ".buildbot-patched")):
1744 return False
1745 return os.path.isdir(os.path.join(self.builder.basedir,
1746 self.srcdir, ".svn"))
1747
1749 revision = self.args['revision'] or 'HEAD'
1750
1751 d = os.path.join(self.builder.basedir, self.srcdir)
1752 command = [self.vcexe, 'update'] + \
1753 self.svn_args + \
1754 ['--revision', str(revision),
1755 '--non-interactive', '--no-auth-cache']
1756 c = ShellCommand(self.builder, command, d,
1757 sendRC=False, timeout=self.timeout,
1758 keepStdout=True, usePTY=False)
1759 self.command = c
1760 return c.start()
1761
1763 revision = self.args['revision'] or 'HEAD'
1764 d = self.builder.basedir
1765 if self.mode == "export":
1766 command = [self.vcexe, 'export'] + \
1767 self.svn_args + \
1768 ['--revision', str(revision),
1769 '--non-interactive', '--no-auth-cache',
1770 self.svnurl, self.srcdir]
1771 else:
1772
1773 command = [self.vcexe, 'checkout'] + \
1774 self.svn_args + \
1775 ['--revision', str(revision),
1776 '--non-interactive', '--no-auth-cache',
1777 self.svnurl, self.srcdir]
1778 c = ShellCommand(self.builder, command, d,
1779 sendRC=False, timeout=self.timeout,
1780 keepStdout=True, usePTY=False)
1781 self.command = c
1782 return c.start()
1783
1785 """
1786 Get the (shell) command used to determine SVN revision number
1787 of checked-out code
1788
1789 return: list of strings, passable as the command argument to ShellCommand
1790 """
1791
1792
1793
1794 svnversion_command = getCommand("svnversion")
1795
1796
1797 return [svnversion_command, "."]
1798
1800 c = ShellCommand(self.builder,
1801 self.getSvnVersionCommand(),
1802 os.path.join(self.builder.basedir, self.srcdir),
1803 environ=self.env,
1804 sendStdout=False, sendStderr=False, sendRC=False,
1805 keepStdout=True, usePTY=False)
1806 d = c.start()
1807 def _parse(res):
1808 r_raw = c.stdout.strip()
1809
1810 r = r_raw.rstrip('MS')
1811 r = r.split(':')[-1]
1812 got_version = None
1813 try:
1814 got_version = int(r)
1815 except ValueError:
1816 msg =("SVN.parseGotRevision unable to parse output "
1817 "of svnversion: '%s'" % r_raw)
1818 log.msg(msg)
1819 self.sendStatus({'header': msg + "\n"})
1820 return got_version
1821 d.addCallback(_parse)
1822 return d
1823
1824
1825 registerSlaveCommand("svn", SVN, command_version)
1826
1827 -class Darcs(SourceBase):
1828 """Darcs-specific VC operation. In addition to the arguments
1829 handled by SourceBase, this command reads the following keys:
1830
1831 ['repourl'] (required): the Darcs repository string
1832 """
1833
1834 header = "darcs operation"
1835
1842
1844 if os.path.exists(os.path.join(self.builder.basedir,
1845 self.srcdir, ".buildbot-patched")):
1846 return False
1847 if self.revision:
1848
1849 return False
1850 return os.path.isdir(os.path.join(self.builder.basedir,
1851 self.srcdir, "_darcs"))
1852
1862
1864
1865 d = self.builder.basedir
1866 command = [self.vcexe, 'get', '--verbose', '--partial',
1867 '--repo-name', self.srcdir]
1868 if self.revision:
1869
1870 n = os.path.join(self.builder.basedir, ".darcs-context")
1871 f = open(n, "wb")
1872 f.write(self.revision)
1873 f.close()
1874
1875 command.append('--context')
1876 command.append(n)
1877 command.append(self.repourl)
1878
1879 c = ShellCommand(self.builder, command, d,
1880 sendRC=False, timeout=self.timeout, usePTY=False)
1881 self.command = c
1882 d = c.start()
1883 if self.revision:
1884 d.addCallback(self.removeContextFile, n)
1885 return d
1886
1887 - def removeContextFile(self, res, n):
1888 os.unlink(n)
1889 return res
1890
1892
1893 command = [self.vcexe, "changes", "--context"]
1894 c = ShellCommand(self.builder, command,
1895 os.path.join(self.builder.basedir, self.srcdir),
1896 environ=self.env,
1897 sendStdout=False, sendStderr=False, sendRC=False,
1898 keepStdout=True, usePTY=False)
1899 d = c.start()
1900 d.addCallback(lambda res: c.stdout)
1901 return d
1902
1903 registerSlaveCommand("darcs", Darcs, command_version)
1904
1906 """Monotone-specific VC operation. In addition to the arguments handled
1907 by SourceBase, this command reads the following keys:
1908
1909 ['server_addr'] (required): the address of the server to pull from
1910 ['branch'] (required): the branch the revision is on
1911 ['db_path'] (required): the local database path to use
1912 ['revision'] (required): the revision to check out
1913 ['monotone']: (required): path to monotone executable
1914 """
1915
1916 header = "monotone operation"
1917
1919 SourceBase.setup(self, args)
1920 self.server_addr = args["server_addr"]
1921 self.branch = args["branch"]
1922 self.db_path = args["db_path"]
1923 self.revision = args["revision"]
1924 self.monotone = args["monotone"]
1925 self._made_fulls = False
1926 self._pull_timeout = args["timeout"]
1927
1929 if not self._made_fulls:
1930 basedir = self.builder.basedir
1931 self.full_db_path = os.path.join(basedir, self.db_path)
1932 self.full_srcdir = os.path.join(basedir, self.srcdir)
1933 self._made_fulls = True
1934
1936 self._makefulls()
1937 if os.path.exists(os.path.join(self.full_srcdir,
1938 ".buildbot_patched")):
1939 return False
1940 return (os.path.isfile(self.full_db_path)
1941 and os.path.isdir(os.path.join(self.full_srcdir, "MT")))
1942
1945
1947
1948 command = [self.monotone, "update",
1949 "-r", self.revision,
1950 "-b", self.branch]
1951 c = ShellCommand(self.builder, command, self.full_srcdir,
1952 sendRC=False, timeout=self.timeout, usePTY=False)
1953 self.command = c
1954 return c.start()
1955
1958
1960 command = [self.monotone, "--db=" + self.full_db_path,
1961 "checkout",
1962 "-r", self.revision,
1963 "-b", self.branch,
1964 self.full_srcdir]
1965 c = ShellCommand(self.builder, command, self.builder.basedir,
1966 sendRC=False, timeout=self.timeout, usePTY=False)
1967 self.command = c
1968 return c.start()
1969
1971 self._makefulls()
1972
1973 if os.path.isfile(self.full_db_path):
1974
1975
1976 command = [self.monotone, "db", "migrate",
1977 "--db=" + self.full_db_path]
1978 else:
1979
1980
1981 self._pull_timeout = max(self._pull_timeout, 3 * 60 * 60)
1982 self.sendStatus({"header": "creating database %s\n"
1983 % (self.full_db_path,)})
1984 command = [self.monotone, "db", "init",
1985 "--db=" + self.full_db_path]
1986 c = ShellCommand(self.builder, command, self.builder.basedir,
1987 sendRC=False, timeout=self.timeout, usePTY=False)
1988 self.command = c
1989 d = c.start()
1990 d.addCallback(self._abandonOnFailure)
1991 d.addCallback(self._didDbInit)
1992 d.addCallback(self._didPull, callback)
1993 return d
1994
1996 command = [self.monotone, "--db=" + self.full_db_path,
1997 "pull", "--ticker=dot", self.server_addr, self.branch]
1998 c = ShellCommand(self.builder, command, self.builder.basedir,
1999 sendRC=False, timeout=self._pull_timeout, usePTY=False)
2000 self.sendStatus({"header": "pulling %s from %s\n"
2001 % (self.branch, self.server_addr)})
2002 self.command = c
2003 return c.start()
2004
2007
2008 registerSlaveCommand("monotone", Monotone, command_version)
2009
2010
2011 -class Git(SourceBase):
2012 """Git specific VC operation. In addition to the arguments
2013 handled by SourceBase, this command reads the following keys:
2014
2015 ['repourl'] (required): the upstream GIT repository string
2016 ['branch'] (optional): which version (i.e. branch or tag) to
2017 retrieve. Default: "master".
2018 ['submodules'] (optional): whether to initialize and update
2019 submodules. Default: False.
2020 """
2021
2022 header = "git operation"
2023
2032
2035
2040
2042 return os.path.isdir(os.path.join(self._fullSrcdir(), ".git"))
2043
2045 return open(self.sourcedatafile, "r").read()
2046
2047
2048
2049
2050
2051
2053 try:
2054 olddata = self.readSourcedata()
2055 if not olddata.startswith(self.repourl+' '):
2056 return False
2057 except IOError:
2058 return False
2059 return True
2060
2067
2083
2084
2085
2086
2096
2098 command = ['git', 'fetch', '-t', self.repourl, self.branch]
2099 self.sendStatus({"header": "fetching branch %s from %s\n"
2100 % (self.branch, self.repourl)})
2101 c = ShellCommand(self.builder, command, self._fullSrcdir(),
2102 sendRC=False, timeout=self.timeout, usePTY=False)
2103 self.command = c
2104 d = c.start()
2105 d.addCallback(self._abandonOnFailure)
2106 d.addCallback(self._didFetch)
2107 return d
2108
2111
2121
2123 command = ['git', 'rev-parse', 'HEAD']
2124 c = ShellCommand(self.builder, command, self._fullSrcdir(),
2125 sendRC=False, keepStdout=True, usePTY=False)
2126 d = c.start()
2127 def _parse(res):
2128 hash = c.stdout.strip()
2129 if len(hash) != 40:
2130 return None
2131 return hash
2132 d.addCallback(_parse)
2133 return d
2134
2135 registerSlaveCommand("git", Git, command_version)
2136
2137 -class Arch(SourceBase):
2138 """Arch-specific (tla-specific) VC operation. In addition to the
2139 arguments handled by SourceBase, this command reads the following keys:
2140
2141 ['url'] (required): the repository string
2142 ['version'] (required): which version (i.e. branch) to retrieve
2143 ['revision'] (optional): the 'patch-NN' argument to check out
2144 ['archive']: the archive name to use. If None, use the archive's default
2145 ['build-config']: if present, give to 'tla build-config' after checkout
2146 """
2147
2148 header = "arch operation"
2149 buildconfig = None
2150
2161
2163 if self.revision:
2164
2165
2166
2167
2168
2169 return False
2170 if os.path.exists(os.path.join(self.builder.basedir,
2171 self.srcdir, ".buildbot-patched")):
2172 return False
2173 return os.path.isdir(os.path.join(self.builder.basedir,
2174 self.srcdir, "{arch}"))
2175
2186
2188
2189
2190
2191
2192
2193 command = [self.vcexe, 'register-archive', '--force', self.url]
2194 c = ShellCommand(self.builder, command, self.builder.basedir,
2195 sendRC=False, keepStdout=True,
2196 timeout=self.timeout, usePTY=False)
2197 self.command = c
2198 d = c.start()
2199 d.addCallback(self._abandonOnFailure)
2200 d.addCallback(self._didRegister, c)
2201 return d
2202
2204
2205
2206 r = re.search(r'Registering archive: (\S+)\s*$', c.stdout)
2207 if r:
2208 msg = "tla reports archive name is '%s'" % r.group(1)
2209 log.msg(msg)
2210 self.builder.sendUpdate({'header': msg+"\n"})
2211 if self.archive and r.group(1) != self.archive:
2212 msg = (" mismatch, we wanted an archive named '%s'"
2213 % self.archive)
2214 log.msg(msg)
2215 self.builder.sendUpdate({'header': msg+"\n"})
2216 raise AbandonChain(-1)
2217 self.archive = r.group(1)
2218 assert self.archive, "need archive name to continue"
2219 return self._doGet()
2220
2222 ver = self.version
2223 if self.revision:
2224 ver += "--%s" % self.revision
2225 command = [self.vcexe, 'get', '--archive', self.archive,
2226 '--no-pristine',
2227 ver, self.srcdir]
2228 c = ShellCommand(self.builder, command, self.builder.basedir,
2229 sendRC=False, timeout=self.timeout, usePTY=False)
2230 self.command = c
2231 d = c.start()
2232 d.addCallback(self._abandonOnFailure)
2233 if self.buildconfig:
2234 d.addCallback(self._didGet)
2235 return d
2236
2246
2248
2249
2250
2251 command = [self.vcexe, "logs", "--full", "--reverse"]
2252 c = ShellCommand(self.builder, command,
2253 os.path.join(self.builder.basedir, self.srcdir),
2254 environ=self.env,
2255 sendStdout=False, sendStderr=False, sendRC=False,
2256 keepStdout=True, usePTY=False)
2257 d = c.start()
2258 def _parse(res):
2259 tid = c.stdout.split("\n")[0].strip()
2260 slash = tid.index("/")
2261 dd = tid.rindex("--")
2262
2263 baserev = tid[dd+2:]
2264 return baserev
2265 d.addCallback(_parse)
2266 return d
2267
2268 registerSlaveCommand("arch", Arch, command_version)
2269
2271 """Bazaar (/usr/bin/baz) is an alternative client for Arch repositories.
2272 It is mostly option-compatible, but archive registration is different
2273 enough to warrant a separate Command.
2274
2275 ['archive'] (required): the name of the archive being used
2276 """
2277
2288
2289
2290
2291
2308
2310
2311 command = [self.vcexe, "tree-id"]
2312 c = ShellCommand(self.builder, command,
2313 os.path.join(self.builder.basedir, self.srcdir),
2314 environ=self.env,
2315 sendStdout=False, sendStderr=False, sendRC=False,
2316 keepStdout=True, usePTY=False)
2317 d = c.start()
2318 def _parse(res):
2319 tid = c.stdout.strip()
2320 slash = tid.index("/")
2321 dd = tid.rindex("--")
2322
2323 baserev = tid[dd+2:]
2324 return baserev
2325 d.addCallback(_parse)
2326 return d
2327
2328 registerSlaveCommand("bazaar", Bazaar, command_version)
2329
2330
2331 -class Bzr(SourceBase):
2332 """bzr-specific VC operation. In addition to the arguments
2333 handled by SourceBase, this command reads the following keys:
2334
2335 ['repourl'] (required): the Bzr repository string
2336 """
2337
2338 header = "bzr operation"
2339
2347
2349 if os.path.exists(os.path.join(self.builder.basedir,
2350 self.srcdir, ".buildbot-patched")):
2351 return False
2352 if self.revision:
2353
2354 return False
2355 return os.path.isdir(os.path.join(self.builder.basedir,
2356 self.srcdir, ".bzr"))
2357
2362
2363 if self.forceSharedRepo:
2364 d = self.doForceSharedRepo();
2365 d.addCallback(cont)
2366 return d
2367 else:
2368 return cont(None)
2369
2379
2408
2410 tmpdir = os.path.join(self.builder.basedir, "export-temp")
2411 srcdir = os.path.join(self.builder.basedir, self.srcdir)
2412 command = [self.vcexe, 'checkout', '--lightweight']
2413 if self.revision:
2414 command.append('--revision')
2415 command.append(str(self.revision))
2416 command.append(self.repourl)
2417 command.append(tmpdir)
2418 c = ShellCommand(self.builder, command, self.builder.basedir,
2419 sendRC=False, timeout=self.timeout, usePTY=False)
2420 self.command = c
2421 d = c.start()
2422 def _export(res):
2423 command = [self.vcexe, 'export', srcdir]
2424 c = ShellCommand(self.builder, command, tmpdir,
2425 sendRC=False, timeout=self.timeout, usePTY=False)
2426 self.command = c
2427 return c.start()
2428 d.addCallback(_export)
2429 return d
2430
2432
2433
2434
2435 c = ShellCommand(self.builder, [self.vcexe, 'info', '.'],
2436 self.builder.basedir,
2437 sendStderr=False, sendRC=False, usePTY=False)
2438 d = c.start()
2439 def afterCheckSharedRepo(res):
2440 if type(res) is int and res != 0:
2441 log.msg("No shared repo found, creating it")
2442
2443 c = ShellCommand(self.builder, [self.vcexe, 'init-repo', '.'],
2444 self.builder.basedir,
2445 sendRC=False, usePTY=False)
2446 self.command = c
2447 return c.start()
2448 else:
2449 return defer.succeed(res)
2450 d.addCallback(afterCheckSharedRepo)
2451 return d
2452
2454
2455
2456
2457 for line in out.split("\n"):
2458 colon = line.find(":")
2459 if colon != -1:
2460 key, value = line[:colon], line[colon+2:]
2461 if key == "revno":
2462 return int(value)
2463 raise ValueError("unable to find revno: in bzr output: '%s'" % out)
2464
2466 command = [self.vcexe, "version-info"]
2467 c = ShellCommand(self.builder, command,
2468 os.path.join(self.builder.basedir, self.srcdir),
2469 environ=self.env,
2470 sendStdout=False, sendStderr=False, sendRC=False,
2471 keepStdout=True, usePTY=False)
2472 d = c.start()
2473 def _parse(res):
2474 try:
2475 return self.get_revision_number(c.stdout)
2476 except ValueError:
2477 msg =("Bzr.parseGotRevision unable to parse output "
2478 "of bzr version-info: '%s'" % c.stdout.strip())
2479 log.msg(msg)
2480 self.sendStatus({'header': msg + "\n"})
2481 return None
2482 d.addCallback(_parse)
2483 return d
2484
2485 registerSlaveCommand("bzr", Bzr, command_version)
2486
2488 """Mercurial specific VC operation. In addition to the arguments
2489 handled by SourceBase, this command reads the following keys:
2490
2491 ['repourl'] (required): the Mercurial repository string
2492 """
2493
2494 header = "mercurial operation"
2495
2497 SourceBase.setup(self, args)
2498 self.vcexe = getCommand("hg")
2499 self.repourl = args['repourl']
2500 self.clobberOnBranchChange = args.get('clobberOnBranchChange', True)
2501 self.sourcedata = "%s\n" % self.repourl
2502 self.branchType = args.get('branchType', 'dirname')
2503 self.stdout = ""
2504 self.stderr = ""
2505
2507 return os.path.isdir(os.path.join(self.builder.basedir,
2508 self.srcdir, ".hg"))
2509
2511 d = os.path.join(self.builder.basedir, self.srcdir)
2512 command = [self.vcexe, 'pull', '--verbose', self.repourl]
2513 c = ShellCommand(self.builder, command, d,
2514 sendRC=False, timeout=self.timeout,
2515 keepStdout=True, usePTY=False)
2516 self.command = c
2517 d = c.start()
2518 d.addCallback(self._handleEmptyUpdate)
2519 d.addCallback(self._update)
2520 return d
2521
2523 if type(res) is int and res == 1:
2524 if self.command.stdout.find("no changes found") != -1:
2525
2526
2527
2528
2529 return 0
2530 return res
2531
2533 d = os.path.join(self.builder.basedir, self.srcdir)
2534 command = [self.vcexe, 'clone', '--verbose', '--noupdate']
2535
2536
2537
2538 if self.args.get('revision') and self.mode == 'clobber' and self.branchType == 'dirname':
2539 command.extend(['--rev', self.args.get('revision')])
2540 command.extend([self.repourl, d])
2541
2542 c = ShellCommand(self.builder, command, self.builder.basedir,
2543 sendRC=False, timeout=self.timeout, usePTY=False)
2544 self.command = c
2545 cmd1 = c.start()
2546 cmd1.addCallback(self._update)
2547 return cmd1
2548
2550 def _vcfull(res):
2551 return self.doVCFull()
2552
2553 c = self.doClobber(dummy, dirname)
2554 c.addCallback(_vcfull)
2555
2556 return c
2557
2558 - def _purge(self, dummy, dirname):
2559 d = os.path.join(self.builder.basedir, self.srcdir)
2560 purge = [self.vcexe, 'purge', '--all']
2561 purgeCmd = ShellCommand(self.builder, purge, d,
2562 sendStdout=False, sendStderr=False,
2563 keepStdout=True, keepStderr=True, usePTY=False)
2564
2565 def _clobber(res):
2566 if res != 0:
2567
2568 msg = "'hg purge' failed: %s\n%s. Clobbering." % (purgeCmd.stdout, purgeCmd.stderr)
2569 self.sendStatus({'header': msg + "\n"})
2570 log.msg(msg)
2571
2572 return self._clobber(dummy, dirname)
2573
2574
2575 return self._update2(res)
2576
2577 p = purgeCmd.start()
2578 p.addCallback(_clobber)
2579 return p
2580
2582 if res != 0:
2583 return res
2584
2585
2586 self.update_branch = self.args.get('branch', 'default')
2587
2588 d = os.path.join(self.builder.basedir, self.srcdir)
2589 parentscmd = [self.vcexe, 'identify', '--num', '--branch']
2590 cmd = ShellCommand(self.builder, parentscmd, d,
2591 sendStdout=False, sendStderr=False,
2592 keepStdout=True, keepStderr=True, usePTY=False)
2593
2594 self.clobber = None
2595
2596 def _parseIdentify(res):
2597 if res != 0:
2598 msg = "'hg identify' failed: %s\n%s" % (cmd.stdout, cmd.stderr)
2599 self.sendStatus({'header': msg + "\n"})
2600 log.msg(msg)
2601 return res
2602
2603 log.msg('Output: %s' % cmd.stdout)
2604
2605 match = re.search(r'^(.+) (.+)$', cmd.stdout)
2606 assert match
2607
2608 rev = match.group(1)
2609 current_branch = match.group(2)
2610
2611 if rev == '-1':
2612 msg = "Fresh hg repo, don't worry about in-repo branch name"
2613 log.msg(msg)
2614
2615 elif os.path.exists(os.path.join(self.builder.basedir,
2616 self.srcdir, ".buildbot-patched")):
2617 self.clobber = self._purge
2618
2619 elif self.update_branch != current_branch:
2620 msg = "Working dir is on in-repo branch '%s' and build needs '%s'." % (current_branch, self.update_branch)
2621 if self.clobberOnBranchChange:
2622 msg += ' Cloberring.'
2623 else:
2624 msg += ' Updating.'
2625
2626 self.sendStatus({'header': msg + "\n"})
2627 log.msg(msg)
2628
2629
2630 if self.clobberOnBranchChange:
2631 self.clobber = self._purge
2632
2633 else:
2634 msg = "Working dir on same in-repo branch as build (%s)." % (current_branch)
2635 log.msg(msg)
2636
2637 return 0
2638
2639 def _checkRepoURL(res):
2640 parentscmd = [self.vcexe, 'paths', 'default']
2641 cmd2 = ShellCommand(self.builder, parentscmd, d,
2642 sendStdout=False, sendStderr=False,
2643 keepStdout=True, keepStderr=True, usePTY=False)
2644
2645 def _parseRepoURL(res):
2646 if res == 1:
2647 if "not found!" == cmd2.stderr.strip():
2648 msg = "hg default path not set. Not checking repo url for clobber test"
2649 log.msg(msg)
2650 return 0
2651 else:
2652 msg = "'hg paths default' failed: %s\n%s" % (cmd2.stdout, cmd2.stderr)
2653 log.msg(msg)
2654 return 1
2655
2656 oldurl = cmd2.stdout.strip()
2657
2658 log.msg("Repo cloned from: '%s'" % oldurl)
2659
2660 if sys.platform == "win32":
2661 oldurl = oldurl.lower().replace('\\', '/')
2662 repourl = self.repourl.lower().replace('\\', '/')
2663 if repourl.startswith('file://'):
2664 repourl = repourl.split('file://')[1]
2665 else:
2666 repourl = self.repourl
2667
2668 if oldurl != repourl:
2669 self.clobber = self._clobber
2670 msg = "RepoURL changed from '%s' in wc to '%s' in update. Clobbering" % (oldurl, repourl)
2671 log.msg(msg)
2672
2673 return 0
2674
2675 c = cmd2.start()
2676 c.addCallback(_parseRepoURL)
2677 return c
2678
2679 def _maybeClobber(res):
2680 if self.clobber:
2681 msg = "Clobber flag set. Doing clobbering"
2682 log.msg(msg)
2683
2684 def _vcfull(res):
2685 return self.doVCFull()
2686
2687 return self.clobber(None, self.srcdir)
2688
2689 return 0
2690
2691 c = cmd.start()
2692 c.addCallback(_parseIdentify)
2693 c.addCallback(_checkRepoURL)
2694 c.addCallback(_maybeClobber)
2695 c.addCallback(self._update2)
2696 return c
2697
2699 d = os.path.join(self.builder.basedir, self.srcdir)
2700
2701 updatecmd=[self.vcexe, 'update', '--clean', '--repository', d]
2702 if self.args.get('revision'):
2703 updatecmd.extend(['--rev', self.args['revision']])
2704 else:
2705 updatecmd.extend(['--rev', self.args.get('branch', 'default')])
2706 self.command = ShellCommand(self.builder, updatecmd,
2707 self.builder.basedir, sendRC=False,
2708 timeout=self.timeout, usePTY=False)
2709 return self.command.start()
2710
2712
2713 command = [self.vcexe, "identify"]
2714 c = ShellCommand(self.builder, command,
2715 os.path.join(self.builder.basedir, self.srcdir),
2716 environ=self.env,
2717 sendStdout=False, sendStderr=False, sendRC=False,
2718 keepStdout=True, usePTY=False)
2719 d = c.start()
2720 def _parse(res):
2721 m = re.search(r'^(\w+)', c.stdout)
2722 return m.group(1)
2723 d.addCallback(_parse)
2724 return d
2725
2726 registerSlaveCommand("hg", Mercurial, command_version)
2727
2728
2730 """Base class for P4 source-updaters
2731
2732 ['p4port'] (required): host:port for server to access
2733 ['p4user'] (optional): user to use for access
2734 ['p4passwd'] (optional): passwd to try for the user
2735 ['p4client'] (optional): client spec to use
2736 """
2738 SourceBase.setup(self, args)
2739 self.p4port = args['p4port']
2740 self.p4client = args['p4client']
2741 self.p4user = args['p4user']
2742 self.p4passwd = args['p4passwd']
2743
2745
2746
2747 command = ['p4']
2748 if self.p4port:
2749 command.extend(['-p', self.p4port])
2750 if self.p4user:
2751 command.extend(['-u', self.p4user])
2752 if self.p4passwd:
2753 command.extend(['-P', self.p4passwd])
2754 if self.p4client:
2755 command.extend(['-c', self.p4client])
2756 command.extend(['changes', '-m', '1', '#have'])
2757 c = ShellCommand(self.builder, command, self.builder.basedir,
2758 environ=self.env, timeout=self.timeout,
2759 sendStdout=True, sendStderr=False, sendRC=False,
2760 keepStdout=True, usePTY=False)
2761 self.command = c
2762 d = c.start()
2763
2764 def _parse(res):
2765
2766
2767
2768 m = re.match('Change\s+(\d+)\s+', c.stdout)
2769 if m:
2770 return m.group(1)
2771 return None
2772 d.addCallback(_parse)
2773 return d
2774
2775
2777 """A P4 source-updater.
2778
2779 ['p4port'] (required): host:port for server to access
2780 ['p4user'] (optional): user to use for access
2781 ['p4passwd'] (optional): passwd to try for the user
2782 ['p4client'] (optional): client spec to use
2783 ['p4extra_views'] (optional): additional client views to use
2784 """
2785
2786 header = "p4"
2787
2789 P4Base.setup(self, args)
2790 self.p4base = args['p4base']
2791 self.p4extra_views = args['p4extra_views']
2792 self.p4mode = args['mode']
2793 self.p4branch = args['branch']
2794
2795 self.sourcedata = str([
2796
2797 self.p4port,
2798
2799
2800 self.p4client,
2801
2802
2803 self.p4base,
2804 self.p4branch,
2805 self.p4extra_views,
2806
2807
2808 self.builder.basedir,
2809 self.mode,
2810 self.workdir
2811 ])
2812
2813
2815 if os.path.exists(os.path.join(self.builder.basedir,
2816 self.srcdir, ".buildbot-patched")):
2817 return False
2818
2819
2820
2821 return os.path.isdir(os.path.join(self.builder.basedir,
2822 self.srcdir))
2823
2826
2828 command = ['p4']
2829
2830 if self.p4port:
2831 command.extend(['-p', self.p4port])
2832 if self.p4user:
2833 command.extend(['-u', self.p4user])
2834 if self.p4passwd:
2835 command.extend(['-P', self.p4passwd])
2836 if self.p4client:
2837 command.extend(['-c', self.p4client])
2838 command.extend(['sync'])
2839 if force:
2840 command.extend(['-f'])
2841 if self.revision:
2842 command.extend(['@' + str(self.revision)])
2843 env = {}
2844 c = ShellCommand(self.builder, command, self.builder.basedir,
2845 environ=env, sendRC=False, timeout=self.timeout,
2846 keepStdout=True, usePTY=False)
2847 self.command = c
2848 d = c.start()
2849 d.addCallback(self._abandonOnFailure)
2850 return d
2851
2852
2854 env = {}
2855 command = ['p4']
2856 client_spec = ''
2857 client_spec += "Client: %s\n\n" % self.p4client
2858 client_spec += "Owner: %s\n\n" % self.p4user
2859 client_spec += "Description:\n\tCreated by %s\n\n" % self.p4user
2860 client_spec += "Root:\t%s\n\n" % self.builder.basedir
2861 client_spec += "Options:\tallwrite rmdir\n\n"
2862 client_spec += "LineEnd:\tlocal\n\n"
2863
2864
2865 client_spec += "View:\n\t%s" % (self.p4base)
2866 if self.p4branch:
2867 client_spec += "%s/" % (self.p4branch)
2868 client_spec += "... //%s/%s/...\n" % (self.p4client, self.srcdir)
2869 if self.p4extra_views:
2870 for k, v in self.p4extra_views:
2871 client_spec += "\t%s/... //%s/%s%s/...\n" % (k, self.p4client,
2872 self.srcdir, v)
2873 if self.p4port:
2874 command.extend(['-p', self.p4port])
2875 if self.p4user:
2876 command.extend(['-u', self.p4user])
2877 if self.p4passwd:
2878 command.extend(['-P', self.p4passwd])
2879 command.extend(['client', '-i'])
2880 log.msg(client_spec)
2881 c = ShellCommand(self.builder, command, self.builder.basedir,
2882 environ=env, sendRC=False, timeout=self.timeout,
2883 initialStdin=client_spec, usePTY=False)
2884 self.command = c
2885 d = c.start()
2886 d.addCallback(self._abandonOnFailure)
2887 d.addCallback(lambda _: self._doP4Sync(force=True))
2888 return d
2889
2891 rv = None
2892 if self.revision:
2893 rv = str(self.revision)
2894 return rv
2895
2896 registerSlaveCommand("p4", P4, command_version)
2897
2898
2900 """A partial P4 source-updater. Requires manual setup of a per-slave P4
2901 environment. The only thing which comes from the master is P4PORT.
2902 'mode' is required to be 'copy'.
2903
2904 ['p4port'] (required): host:port for server to access
2905 ['p4user'] (optional): user to use for access
2906 ['p4passwd'] (optional): passwd to try for the user
2907 ['p4client'] (optional): client spec to use
2908 """
2909
2910 header = "p4 sync"
2911
2915
2918
2919 - def _doVC(self, force):
2920 d = os.path.join(self.builder.basedir, self.srcdir)
2921 command = [self.vcexe]
2922 if self.p4port:
2923 command.extend(['-p', self.p4port])
2924 if self.p4user:
2925 command.extend(['-u', self.p4user])
2926 if self.p4passwd:
2927 command.extend(['-P', self.p4passwd])
2928 if self.p4client:
2929 command.extend(['-c', self.p4client])
2930 command.extend(['sync'])
2931 if force:
2932 command.extend(['-f'])
2933 if self.revision:
2934 command.extend(['@' + self.revision])
2935 env = {}
2936 c = ShellCommand(self.builder, command, d, environ=env,
2937 sendRC=False, timeout=self.timeout, usePTY=False)
2938 self.command = c
2939 return c.start()
2940
2943
2946
2948 rv = None
2949 if self.revision:
2950 rv = str(self.revision)
2951 return rv
2952
2953 registerSlaveCommand("p4sync", P4Sync, command_version)
2954