1   
   2   
   3   
   4   
   5   
   6   
   7   
   8   
   9   
  10   
  11   
  12   
  13   
  14   
  15   
  16   
  17  import re 
  18   
  19  from zope.interface import implements 
  20  from twisted.internet import reactor, defer, error 
  21  from twisted.protocols import basic 
  22  from twisted.spread import pb 
  23  from twisted.python import log 
  24  from twisted.python.failure import Failure 
  25  from twisted.web.util import formatFailure 
  26  from twisted.python.reflect import accumulateClassList 
  27   
  28  from buildbot import interfaces, locks, util 
  29  from buildbot.status import progress 
  30  from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED, \ 
  31       EXCEPTION, RETRY, worst_status 
  32  from buildbot.process import metrics 
  33   
  34  """ 
  35  BuildStep and RemoteCommand classes for master-side representation of the 
  36  build process 
  37  """ 
  40      """ 
  41      I represent a single command to be run on the slave. I handle the details 
  42      of reliably gathering status updates from the slave (acknowledging each), 
  43      and (eventually, in a future release) recovering from interrupted builds. 
  44      This is the master-side object that is known to the slave-side 
  45      L{buildbot.slave.bot.SlaveBuilder}, to which status updates are sent. 
  46   
  47      My command should be started by calling .run(), which returns a 
  48      Deferred that will fire when the command has finished, or will 
  49      errback if an exception is raised. 
  50       
  51      Typically __init__ or run() will set up self.remote_command to be a 
  52      string which corresponds to one of the SlaveCommands registered in 
  53      the buildslave, and self.args to a dictionary of arguments that will 
  54      be passed to the SlaveCommand instance. 
  55   
  56      start, remoteUpdate, and remoteComplete are available to be overridden 
  57   
  58      @type  commandCounter: list of one int 
  59      @cvar  commandCounter: provides a unique value for each 
  60                             RemoteCommand executed across all slaves 
  61      @type  active:         boolean 
  62      @ivar  active:         whether the command is currently running 
  63      """ 
  64   
  65       
  66      commandCounter = 0 
  67   
  68      active = False 
  69   
  70 -    def __init__(self, remote_command, args, ignore_updates=False): 
   71          """ 
  72          @type  remote_command: string 
  73          @param remote_command: remote command to start.  This will be 
  74                                 passed to 
  75                                 L{buildbot.slave.bot.SlaveBuilder.remote_startCommand} 
  76                                 and needs to have been registered 
  77                                 slave-side in L{buildbot.slave.registry} 
  78          @type  args:           dict 
  79          @param args:           arguments to send to the remote command 
  80   
  81          @param ignore_updates: if true, ignore any updates from the slave side 
  82          """ 
  83   
  84          self.remote_command = remote_command 
  85          self.args = args 
  86          self.ignore_updates = ignore_updates 
   87   
  88 -    def run(self, step, remote): 
  114   
 116          """ 
 117          Tell the slave to start executing the remote command. 
 118   
 119          @rtype:   L{twisted.internet.defer.Deferred} 
 120          @returns: a deferred that will fire when the remote command is 
 121                    done (with None as the result) 
 122          """ 
 123   
 124           
 125          cmd_args = self.args 
 126          if cmd_args.has_key("logfiles") and cmd_args["logfiles"]: 
 127              cmd_args = cmd_args.copy() 
 128              cmd_args["logfiles"] = self.step.build.render(cmd_args["logfiles"]) 
 129   
 130           
 131           
 132           
 133           
 134          d = self.remote.callRemote("startCommand", self, self.commandID, 
 135                                     self.remote_command, cmd_args) 
 136          return d 
  137   
 139           
 140           
 141           
 142   
 143          log.msg("RemoteCommand.interrupt", self, why) 
 144          if not self.active: 
 145              log.msg(" but this RemoteCommand is already inactive") 
 146              return 
 147          if not self.remote: 
 148              log.msg(" but our .remote went away") 
 149              return 
 150          if isinstance(why, Failure) and why.check(error.ConnectionLost): 
 151              log.msg("RemoteCommand.disconnect: lost slave") 
 152              self.remote = None 
 153              self._finished(why) 
 154              return 
 155   
 156           
 157           
 158           
 159          d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand", 
 160                                  self.commandID, str(why)) 
 161           
 162          d.addErrback(self._interruptFailed) 
 163          return d 
  164   
 166          log.msg("RemoteCommand._interruptFailed", self) 
 167           
 168           
 169          return None 
  170   
 172          """ 
 173          I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so 
 174          I can receive updates from the running remote command. 
 175   
 176          @type  updates: list of [object, int] 
 177          @param updates: list of updates from the remote command 
 178          """ 
 179          self.buildslave.messageReceivedFromSlave() 
 180          max_updatenum = 0 
 181          for (update, num) in updates: 
 182               
 183              try: 
 184                  if self.active and not self.ignore_updates: 
 185                      self.remoteUpdate(update) 
 186              except: 
 187                   
 188                  self._finished(Failure()) 
 189                   
 190                   
 191              if num > max_updatenum: 
 192                  max_updatenum = num 
 193          return max_updatenum 
  194   
 196          raise NotImplementedError("You must implement this in a subclass") 
  197   
 199          """ 
 200          Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to 
 201          notify me the remote command has finished. 
 202   
 203          @type  failure: L{twisted.python.failure.Failure} or None 
 204   
 205          @rtype: None 
 206          """ 
 207          self.buildslave.messageReceivedFromSlave() 
 208           
 209           
 210          if self.active: 
 211              reactor.callLater(0, self._finished, failure) 
 212          return None 
  213   
 215          self.active = False 
 216           
 217           
 218           
 219           
 220           
 221          d = defer.maybeDeferred(self.remoteComplete, failure) 
 222           
 223           
 224          d.addCallback(lambda r: self) 
 225           
 226           
 227          d.addBoth(self.deferred.callback) 
  228   
 230          """Subclasses can override this. 
 231   
 232          This is called when the RemoteCommand has finished. 'maybeFailure' 
 233          will be None if the command completed normally, or a Failure 
 234          instance in one of the following situations: 
 235   
 236           - the slave was lost before the command was started 
 237           - the slave didn't respond to the startCommand message 
 238           - the slave raised an exception while starting the command 
 239             (bad command name, bad args, OSError from missing executable) 
 240           - the slave raised an exception while finishing the command 
 241             (they send back a remote_complete message with a Failure payload) 
 242   
 243          and also (for now): 
 244           -  slave disconnected while the command was running 
 245           
 246          This method should do cleanup, like closing log files. It should 
 247          normally return the 'failure' argument, so that any exceptions will 
 248          be propagated to the Step. If it wants to consume them, return None 
 249          instead.""" 
 250   
 251          return maybeFailure 
   252   
 254      """ 
 255   
 256      I am a L{RemoteCommand} which gathers output from the remote command into 
 257      one or more local log files. My C{self.logs} dictionary contains 
 258      references to these L{buildbot.status.logfile.LogFile} instances. Any 
 259      stdout/stderr/header updates from the slave will be put into 
 260      C{self.logs['stdio']}, if it exists. If the remote command uses other log 
 261      files, they will go into other entries in C{self.logs}. 
 262   
 263      If you want to use stdout or stderr, you should create a LogFile named 
 264      'stdio' and pass it to my useLog() message. Otherwise stdout/stderr will 
 265      be ignored, which is probably not what you want. 
 266   
 267      Unless you tell me otherwise, when my command completes I will close all 
 268      the LogFiles that I know about. 
 269   
 270      @ivar logs: maps logname to a LogFile instance 
 271      @ivar _closeWhenFinished: maps logname to a boolean. If true, this 
 272                                LogFile will be closed when the RemoteCommand 
 273                                finishes. LogFiles which are shared between 
 274                                multiple RemoteCommands should use False here. 
 275   
 276      """ 
 277   
 278      rc = None 
 279      debug = False 
 280   
 282          self.logs = {} 
 283          self.delayedLogs = {} 
 284          self._closeWhenFinished = {} 
 285          RemoteCommand.__init__(self, *args, **kwargs) 
  286   
 288          return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self)) 
  289   
 290 -    def useLog(self, loog, closeWhenFinished=False, logfileName=None): 
  291          """Start routing messages from a remote logfile to a local LogFile 
 292   
 293          I take a local ILogFile instance in 'loog', and arrange to route 
 294          remote log messages for the logfile named 'logfileName' into it. By 
 295          default this logfileName comes from the ILogFile itself (using the 
 296          name by which the ILogFile will be displayed), but the 'logfileName' 
 297          argument can be used to override this. For example, if 
 298          logfileName='stdio', this logfile will collect text from the stdout 
 299          and stderr of the command. 
 300   
 301          @param loog: an instance which implements ILogFile 
 302          @param closeWhenFinished: a boolean, set to False if the logfile 
 303                                    will be shared between multiple 
 304                                    RemoteCommands. If True, the logfile will 
 305                                    be closed when this ShellCommand is done 
 306                                    with it. 
 307          @param logfileName: a string, which indicates which remote log file 
 308                              should be routed into this ILogFile. This should 
 309                              match one of the keys of the logfiles= argument 
 310                              to ShellCommand. 
 311   
 312          """ 
 313   
 314          assert interfaces.ILogFile.providedBy(loog) 
 315          if not logfileName: 
 316              logfileName = loog.getName() 
 317          assert logfileName not in self.logs 
 318          assert logfileName not in self.delayedLogs 
 319          self.logs[logfileName] = loog 
 320          self._closeWhenFinished[logfileName] = closeWhenFinished 
 321          self._startTime = None 
 322          self._remoteElapsed = None 
  323   
 324 -    def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False): 
  325          assert logfileName not in self.logs 
 326          assert logfileName not in self.delayedLogs 
 327          self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished) 
  328   
 330          log.msg("LoggedRemoteCommand.start") 
 331          if 'stdio' not in self.logs: 
 332              log.msg("LoggedRemoteCommand (%s) is running a command, but " 
 333                      "it isn't being logged to anything. This seems unusual." 
 334                      % self) 
 335          self.updates = {} 
 336          self._startTime = util.now() 
 337          return RemoteCommand.start(self) 
  338   
 348   
 350           
 351          if logname in self.delayedLogs: 
 352              (activateCallBack, closeWhenFinished) = self.delayedLogs[logname] 
 353              del self.delayedLogs[logname] 
 354              loog = activateCallBack(self) 
 355              self.logs[logname] = loog 
 356              self._closeWhenFinished[logname] = closeWhenFinished 
 357   
 358          if logname in self.logs: 
 359              self.logs[logname].addStdout(data) 
 360          else: 
 361              log.msg("%s.addToLog: no such log %s" % (self, logname)) 
  362   
 363      @metrics.countMethod('LoggedRemoteCommand.remoteUpdate()') 
 393   
 395          if self._startTime and self._remoteElapsed: 
 396              delta = (util.now() - self._startTime) - self._remoteElapsed 
 397              metrics.MetricTimeEvent.log("LoggedRemoteCommand.overhead", delta) 
 398   
 399          for name,loog in self.logs.items(): 
 400              if self._closeWhenFinished[name]: 
 401                  if maybeFailure: 
 402                      loog.addHeader("\nremoteFailed: %s" % maybeFailure) 
 403                  else: 
 404                      log.msg("closing log %s" % loog) 
 405                  loog.finish() 
 406          return maybeFailure 
   407   
 410      implements(interfaces.ILogObserver) 
 411   
 414   
 418   
 419 -    def logChunk(self, build, step, log, channel, text): 
  424   
 425       
 426   
 428          """This will be called with chunks of stdout data. Override this in 
 429          your observer.""" 
 430          pass 
  431   
 433          """This will be called with chunks of stderr data. Override this in 
 434          your observer.""" 
 435          pass 
   436   
 450   
 452          """ 
 453          Set the maximum line length: lines longer than max_length are 
 454          dropped.  Default is 16384 bytes.  Use sys.maxint for effective 
 455          infinity. 
 456          """ 
 457          self.stdoutParser.MAX_LENGTH = max_length 
 458          self.stderrParser.MAX_LENGTH = max_length 
  459   
 461          self.stdoutParser.dataReceived(data) 
  462   
 464          self.stderrParser.dataReceived(data) 
  465   
 467          """This will be called with complete stdout lines (not including the 
 468          delimiter). Override this in your observer.""" 
 469          pass 
  470   
 472          """This will be called with complete lines of stderr (not including 
 473          the delimiter). Override this in your observer.""" 
 474          pass 
   475   
 478      """This class helps you run a shell command on the build slave. It will 
 479      accumulate all the command's output into a Log named 'stdio'. When the 
 480      command is finished, it will fire a Deferred. You can then check the 
 481      results of the command and parse the output however you like.""" 
 482   
 483 -    def __init__(self, workdir, command, env=None, 
 484                   want_stdout=1, want_stderr=1, 
 485                   timeout=20*60, maxTime=None, logfiles={}, 
 486                   usePTY="slave-config", logEnviron=True): 
  487          """ 
 488          @type  workdir: string 
 489          @param workdir: directory where the command ought to run, 
 490                          relative to the Builder's home directory. Defaults to 
 491                          '.': the same as the Builder's homedir. This should 
 492                          probably be '.' for the initial 'cvs checkout' 
 493                          command (which creates a workdir), and the Build-wide 
 494                          workdir for all subsequent commands (including 
 495                          compiles and 'cvs update'). 
 496   
 497          @type  command: list of strings (or string) 
 498          @param command: the shell command to run, like 'make all' or 
 499                          'cvs update'. This should be a list or tuple 
 500                          which can be used directly as the argv array. 
 501                          For backwards compatibility, if this is a 
 502                          string, the text will be given to '/bin/sh -c 
 503                          %s'. 
 504   
 505          @type  env:     dict of string->string 
 506          @param env:     environment variables to add or change for the 
 507                          slave.  Each command gets a separate 
 508                          environment; all inherit the slave's initial 
 509                          one.  TODO: make it possible to delete some or 
 510                          all of the slave's environment. 
 511   
 512          @type  want_stdout: bool 
 513          @param want_stdout: defaults to True. Set to False if stdout should 
 514                              be thrown away. Do this to avoid storing or 
 515                              sending large amounts of useless data. 
 516   
 517          @type  want_stderr: bool 
 518          @param want_stderr: False if stderr should be thrown away 
 519   
 520          @type  timeout: int 
 521          @param timeout: tell the remote that if the command fails to 
 522                          produce any output for this number of seconds, 
 523                          the command is hung and should be killed. Use 
 524                          None to disable the timeout. 
 525   
 526          @param logEnviron: whether to log env vars on the slave side 
 527   
 528          @type  maxTime: int 
 529          @param maxTime: tell the remote that if the command fails to complete 
 530                          in this number of seconds, the command should be 
 531                          killed.  Use None to disable maxTime. 
 532          """ 
 533   
 534          self.command = command  
 535          if env is not None: 
 536               
 537               
 538               
 539              env = env.copy() 
 540          args = {'workdir': workdir, 
 541                  'env': env, 
 542                  'want_stdout': want_stdout, 
 543                  'want_stderr': want_stderr, 
 544                  'logfiles': logfiles, 
 545                  'timeout': timeout, 
 546                  'maxTime': maxTime, 
 547                  'usePTY': usePTY, 
 548                  'logEnviron': logEnviron, 
 549                  } 
 550          LoggedRemoteCommand.__init__(self, "shell", args) 
  551   
 553          self.args['command'] = self.command 
 554          if self.remote_command == "shell": 
 555               
 556               
 557              if self.step.slaveVersion("shell", "old") == "old": 
 558                  self.args['dir'] = self.args['workdir'] 
 559          what = "command '%s' in dir '%s'" % (self.args['command'], 
 560                                               self.args['workdir']) 
 561          log.msg(what) 
 562          return LoggedRemoteCommand.start(self) 
  563   
 565          return "<RemoteShellCommand '%s'>" % repr(self.command) 
   566   
 568      """ 
 569      I represent a single step of the build process. This step may involve 
 570      zero or more commands to be run in the build slave, as well as arbitrary 
 571      processing on the master side. Regardless of how many slave commands are 
 572      run, the BuildStep will result in a single status value. 
 573   
 574      The step is started by calling startStep(), which returns a Deferred that 
 575      fires when the step finishes. See C{startStep} for a description of the 
 576      results provided by that Deferred. 
 577   
 578      __init__ and start are good methods to override. Don't forget to upcall 
 579      BuildStep.__init__ or bad things will happen. 
 580   
 581      To launch a RemoteCommand, pass it to .runCommand and wait on the 
 582      Deferred it returns. 
 583   
 584      Each BuildStep generates status as it runs. This status data is fed to 
 585      the L{buildbot.status.buildstep.BuildStepStatus} listener that sits in 
 586      C{self.step_status}. It can also feed progress data (like how much text 
 587      is output by a shell command) to the 
 588      L{buildbot.status.progress.StepProgress} object that lives in 
 589      C{self.progress}, by calling C{self.setProgress(metric, value)} as it 
 590      runs. 
 591   
 592      @type build: L{buildbot.process.build.Build} 
 593      @ivar build: the parent Build which is executing this step 
 594   
 595      @type progress: L{buildbot.status.progress.StepProgress} 
 596      @ivar progress: tracks ETA for the step 
 597   
 598      @type step_status: L{buildbot.status.buildstep.BuildStepStatus} 
 599      @ivar step_status: collects output status 
 600      """ 
 601   
 602       
 603       
 604       
 605       
 606       
 607       
 608       
 609      haltOnFailure = False 
 610      flunkOnWarnings = False 
 611      flunkOnFailure = False 
 612      warnOnWarnings = False 
 613      warnOnFailure = False 
 614      alwaysRun = False 
 615   
 616       
 617       
 618       
 619       
 620       
 621       
 622       
 623      parms = ['name', 'locks', 
 624               'haltOnFailure', 
 625               'flunkOnWarnings', 
 626               'flunkOnFailure', 
 627               'warnOnWarnings', 
 628               'warnOnFailure', 
 629               'alwaysRun', 
 630               'progressMetrics', 
 631               'doStepIf', 
 632               ] 
 633   
 634      name = "generic" 
 635      locks = [] 
 636      progressMetrics = ()  
 637      useProgress = True  
 638      build = None 
 639      step_status = None 
 640      progress = None 
 641       
 642      doStepIf = True 
 643   
 645          self.factory = (self.__class__, dict(kwargs)) 
 646          for p in self.__class__.parms: 
 647              if kwargs.has_key(p): 
 648                  setattr(self, p, kwargs[p]) 
 649                  del kwargs[p] 
 650          if kwargs: 
 651              why = "%s.__init__ got unexpected keyword argument(s) %s" \ 
 652                    % (self, kwargs.keys()) 
 653              raise TypeError(why) 
 654          self._pendingLogObservers = [] 
 655   
 656          self._acquiringLock = None 
 657          self.stopped = False 
  658   
 661   
 669   
 672   
 674           
 675           
 676           
 677           
 678           
 679          pass 
  680   
 683   
 686   
 689   
 697   
 699          """BuildSteps can call self.setProgress() to announce progress along 
 700          some metric.""" 
 701          if self.progress: 
 702              self.progress.setProgress(metric, value) 
  703   
 706   
 707 -    def setProperty(self, propname, value, source="Step", runtime=True): 
  709   
 711          """Begin the step. This returns a Deferred that will fire when the 
 712          step finishes. 
 713   
 714          This deferred fires with a tuple of (result, [extra text]), although 
 715          older steps used to return just the 'result' value, so the receiving 
 716          L{base.Build} needs to be prepared to handle that too. C{result} is 
 717          one of the SUCCESS/WARNINGS/FAILURE/SKIPPED constants from 
 718          L{buildbot.status.builder}, and the extra text is a list of short 
 719          strings which should be appended to the Build's text results. This 
 720          text allows a test-case step which fails to append B{17 tests} to the 
 721          Build's status, in addition to marking the build as failing. 
 722   
 723          The deferred will errback if the step encounters an exception, 
 724          including an exception on the slave side (or if the slave goes away 
 725          altogether). Failures in shell commands (rc!=0) will B{not} cause an 
 726          errback, in general the BuildStep will evaluate the results and 
 727          decide whether to treat it as a WARNING or FAILURE. 
 728   
 729          @type remote: L{twisted.spread.pb.RemoteReference} 
 730          @param remote: a reference to the slave's 
 731                         L{buildbot.slave.bot.SlaveBuilder} instance where any 
 732                         RemoteCommands may be run 
 733          """ 
 734   
 735          self.remote = remote 
 736          self.deferred = defer.Deferred() 
 737           
 738          lock_list = [] 
 739          for access in self.locks: 
 740              if not isinstance(access, locks.LockAccess): 
 741                   
 742                  access = access.defaultAccess() 
 743              lock = self.build.builder.botmaster.getLockByID(access.lockid) 
 744              lock_list.append((lock, access)) 
 745          self.locks = lock_list 
 746           
 747           
 748          self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks] 
 749          for l, la in self.locks: 
 750              if l in self.build.locks: 
 751                  log.msg("Hey, lock %s is claimed by both a Step (%s) and the" 
 752                          " parent Build (%s)" % (l, self, self.build)) 
 753                  raise RuntimeError("lock claimed by both Step and Build") 
 754   
 755           
 756           
 757          self.step_status.setText(self.describe(False)) 
 758          self.step_status.stepStarted() 
 759   
 760          d = self.acquireLocks() 
 761          d.addCallback(self._startStep_2) 
 762          d.addErrback(self.failed) 
 763          return self.deferred 
  764   
 785   
 807   
 825   
 827          """Begin the step. Override this method and add code to do local 
 828          processing, fire off remote commands, etc. 
 829   
 830          To spawn a command in the buildslave, create a RemoteCommand instance 
 831          and run it with self.runCommand:: 
 832   
 833            c = RemoteCommandFoo(args) 
 834            d = self.runCommand(c) 
 835            d.addCallback(self.fooDone).addErrback(self.failed) 
 836   
 837          As the step runs, it should send status information to the 
 838          BuildStepStatus:: 
 839   
 840            self.step_status.setText(['compile', 'failed']) 
 841            self.step_status.setText2(['4', 'warnings']) 
 842   
 843          To have some code parse stdio (or other log stream) in realtime, add 
 844          a LogObserver subclass. This observer can use self.step.setProgress() 
 845          to provide better progress notification to the step.:: 
 846   
 847            self.addLogObserver('stdio', MyLogObserver()) 
 848   
 849          To add a LogFile, use self.addLog. Make sure it gets closed when it 
 850          finishes. When giving a Logfile to a RemoteShellCommand, just ask it 
 851          to close the log when the command completes:: 
 852   
 853            log = self.addLog('output') 
 854            cmd = RemoteShellCommand(args) 
 855            cmd.useLog(log, closeWhenFinished=True) 
 856   
 857          You can also create complete Logfiles with generated text in a single 
 858          step:: 
 859   
 860            self.addCompleteLog('warnings', text) 
 861   
 862          When the step is done, it should call self.finished(result). 'result' 
 863          will be provided to the L{buildbot.process.build.Build}, and should be 
 864          one of the constants defined above: SUCCESS, WARNINGS, FAILURE, or 
 865          SKIPPED. 
 866   
 867          If the step encounters an exception, it should call self.failed(why). 
 868          'why' should be a Failure object. This automatically fails the whole 
 869          build with an exception. It is a good idea to add self.failed as an 
 870          errback to any Deferreds you might obtain. 
 871   
 872          If the step decides it does not need to be run, start() can return 
 873          the constant SKIPPED. This fires the callback immediately: it is not 
 874          necessary to call .finished yourself. This can also indicate to the 
 875          status-reporting mechanism that this step should not be displayed. 
 876   
 877          A step can be configured to only run under certain conditions.  To 
 878          do this, set the step's doStepIf to a boolean value, or to a function 
 879          that returns a boolean value.  If the value or function result is 
 880          False, then the step will return SKIPPED without doing anything, 
 881          otherwise the step will be executed normally.  If you set doStepIf 
 882          to a function, that function should accept one parameter, which will 
 883          be the Step object itself.""" 
 884           
 885          raise NotImplementedError("your subclass must implement this method") 
  886   
 888          """Halt the command, either because the user has decided to cancel 
 889          the build ('reason' is a string), or because the slave has 
 890          disconnected ('reason' is a ConnectionLost Failure). Any further 
 891          local processing should be skipped, and the Step completed with an 
 892          error status. The results text should say something useful like 
 893          ['step', 'interrupted'] or ['remote', 'lost']""" 
 894          self.stopped = True 
 895          if self._acquiringLock: 
 896              lock, access, d = self._acquiringLock 
 897              lock.stopWaitingUntilAvailable(self, access, d) 
 898              d.callback(None) 
  899   
 908   
 922   
 931   
 961   
 962       
 963   
 965          """Return the version number of the given slave command. For the 
 966          commands defined in buildbot.slave.commands, this is the value of 
 967          'cvs_ver' at the top of that file. Non-existent commands will return 
 968          a value of None. Buildslaves running buildbot-0.5.0 or earlier did 
 969          not respond to the version query: commands on those slaves will 
 970          return a value of OLDVERSION, so you can distinguish between old 
 971          buildslaves and missing commands. 
 972   
 973          If you know that <=0.5.0 buildslaves have the command you want (CVS 
 974          and SVN existed back then, but none of the other VC systems), then it 
 975          makes sense to call this with oldversion='old'. If the command you 
 976          want is newer than that, just leave oldversion= unspecified, and the 
 977          command will return None for a buildslave that does not implement the 
 978          command. 
 979          """ 
 980          return self.build.getSlaveCommandVersion(command, oldversion) 
  981   
 983          sv = self.build.getSlaveCommandVersion(command, None) 
 984          if sv is None: 
 985              return True 
 986           
 987           
 988           
 989           
 990           
 991          if map(int, sv.split(".")) < map(int, minversion.split(".")): 
 992              return True 
 993          return False 
  994   
 997   
1002   
1008   
1017   
1022   
1024          assert interfaces.ILogObserver.providedBy(observer) 
1025          observer.setStep(self) 
1026          self._pendingLogObservers.append((logname, observer)) 
1027          self._connectPendingLogObservers() 
 1028   
1030          if not self._pendingLogObservers: 
1031              return 
1032          if not self.step_status: 
1033              return 
1034          current_logs = {} 
1035          for loog in self.step_status.getLogs(): 
1036              current_logs[loog.getName()] = loog 
1037          for logname, observer in self._pendingLogObservers[:]: 
1038              if logname in current_logs: 
1039                  observer.setLog(current_logs[logname]) 
1040                  self._pendingLogObservers.remove((logname, observer)) 
 1041   
1042 -    def addURL(self, name, url): 
 1043          """Add a BuildStep URL to this step. 
1044   
1045          An HREF to this URL will be added to any HTML representations of this 
1046          step. This allows a step to provide links to external web pages, 
1047          perhaps to provide detailed HTML code coverage results or other forms 
1048          of build status. 
1049          """ 
1050          self.step_status.addURL(name, url) 
 1051   
 1056   
1059      length = 0 
1060   
1063   
1064 -    def logChunk(self, build, step, log, channel, text): 
  1067   
1069      """This is an abstract base class, suitable for inheritance by all 
1070      BuildSteps that invoke RemoteCommands which emit stdout/stderr messages. 
1071      """ 
1072   
1073      progressMetrics = ('output',) 
1074      logfiles = {} 
1075   
1076      parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func'] 
1077      cmd = None 
1078   
1079 -    def __init__(self, logfiles={}, lazylogfiles=False, log_eval_func=None, 
1080                   *args, **kwargs): 
 1098   
1100          """ 
1101          This allows to add logfiles after construction, but before calling 
1102          startCommand(). 
1103          """ 
1104          self.logfiles[logname] = filename 
 1105   
1107          kwargs = dict() 
1108          kwargs['logfiles'] = self.logfiles 
1109          return kwargs 
 1110   
1140          d.addCallback(_gotResults)  
1141          d.addCallbacks(self.finished, self.checkDisconnect) 
1142          d.addErrback(self.failed) 
 1143   
1145          """Set up any additional logfiles= logs. 
1146   
1147          @param cmd: the LoggedRemoteCommand to add additional logs to. 
1148   
1149          @param logfiles: a dict of tuples (logname,remotefilename) 
1150                           specifying additional logs to watch. (note: 
1151                           the remotefilename component is currently 
1152                           ignored) 
1153          """ 
1154          for logname,remotefilename in logfiles.items(): 
1155              if self.lazylogfiles: 
1156                   
1157                   
1158                   
1159                   
1160                   
1161                   
1162                  callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname) 
1163                  cmd.useLogDelayed(logname, callback, True) 
1164              else: 
1165                   
1166                  newlog = self.addLog(logname) 
1167                   
1168                  cmd.useLog(newlog, True) 
 1169   
1183   
1190   
1191       
1192       
1193       
1194       
1195       
1196       
1197       
1198       
1199       
1200       
1201       
1202       
1203       
1204   
1205       
1206       
1207       
1208       
1209       
1210   
1211       
1212       
1213       
1214   
1216          """This is a general-purpose hook method for subclasses. It will be 
1217          called after the remote command has finished, but before any of the 
1218          other hook functions are called.""" 
1219          pass 
 1220   
1222          """To create summary logs, do something like this: 
1223          warnings = grep('^Warning:', log.getText()) 
1224          self.addCompleteLog('warnings', warnings) 
1225          """ 
1226          pass 
 1227   
1229          """Decide whether the command was SUCCESS, WARNINGS, or FAILURE. 
1230          Override this to, say, declare WARNINGS if there is any stderr 
1231          activity, or to say that rc!=0 is not actually an error.""" 
1232   
1233          if self.log_eval_func: 
1234              return self.log_eval_func(cmd, self.step_status) 
1235          if cmd.rc != 0: 
1236              return FAILURE 
1237          return SUCCESS 
 1238   
1239 -    def getText(self, cmd, results): 
 1240          if results == SUCCESS: 
1241              return self.describe(True) 
1242          elif results == WARNINGS: 
1243              return self.describe(True) + ["warnings"] 
1244          elif results == EXCEPTION: 
1245              return self.describe(True) + ["exception"] 
1246          else: 
1247              return self.describe(True) + ["failed"] 
 1248   
1249 -    def getText2(self, cmd, results): 
 1250          """We have decided to add a short note about ourselves to the overall 
1251          build description, probably because something went wrong. Return a 
1252          short list of short strings. If your subclass counts test failures or 
1253          warnings of some sort, this is a good place to announce the count.""" 
1254           
1255           
1256          return [self.name] 
 1257   
1258 -    def maybeGetText2(self, cmd, results): 
 1259          if results == SUCCESS: 
1260               
1261              pass 
1262          elif results == WARNINGS: 
1263              if (self.flunkOnWarnings or self.warnOnWarnings): 
1264                   
1265                  return self.getText2(cmd, results) 
1266          else: 
1267              if (self.haltOnFailure or self.flunkOnFailure 
1268                  or self.warnOnFailure): 
1269                   
1270                  return self.getText2(cmd, results) 
1271          return [] 
 1272   
1278   
1288      worst = SUCCESS 
1289      if cmd.rc != 0: 
1290          worst = FAILURE 
1291      for err, possible_status in regexes: 
1292           
1293           
1294           
1295          if worst_status(worst, possible_status) == possible_status: 
1296              if isinstance(err, (basestring)): 
1297                  err = re.compile(".*%s.*" % err, re.DOTALL) 
1298              for l in cmd.logs.values(): 
1299                  if err.search(l.getText()): 
1300                      worst = possible_status 
1301      return worst 
 1302   
1303   
1304   
1305  from buildbot.process.properties import WithProperties 
1306  _hush_pyflakes = [WithProperties] 
1307  del _hush_pyflakes 
1308