ambuild2/task.py
author David Anderson <dvander@alliedmods.net>
Mon Nov 18 12:55:07 2013 -0800 (2013-11-18)
changeset 204 3c9107670976
parent 202 d9bdfaf2147b
child 206 7aaf66030dcd
permissions -rw-r--r--
Fix more RC stuff.
dvander@86
     1
# vim: set ts=8 sts=2 sw=2 tw=99 et:
dvander@94
     2
import errno
dvander@117
     3
import shutil
dvander@94
     4
import os, sys
dvander@94
     5
import traceback
dvander@87
     6
import multiprocessing as mp
dvander@167
     7
from ambuild2 import util
dvander@167
     8
from ambuild2 import nodetypes
dvander@167
     9
from ambuild2.ipc import ParentProcessListener, ChildProcessListener
dvander@167
    10
from ambuild2.ipc import ProcessManager, MessageListener, Error
dvander@167
    11
from ambuild2.ipc import Channel
dvander@86
    12
dvander@86
    13
class Task(object):
dvander@86
    14
  def __init__(self, id, entry, outputs):
dvander@86
    15
    self.id = id
dvander@86
    16
    self.type = entry.type
dvander@86
    17
    self.data = entry.blob
dvander@108
    18
    if entry.folder:
dvander@108
    19
      self.folder = entry.folder.path
dvander@108
    20
    else:
dvander@108
    21
      self.folder = None
dvander@86
    22
    self.outputs = outputs
dvander@86
    23
    self.outgoing = []
dvander@86
    24
    self.incoming = set()
dvander@86
    25
dvander@86
    26
  def addOutgoing(self, task):
dvander@86
    27
    self.outgoing.append(task)
dvander@86
    28
    task.incoming.add(self)
dvander@86
    29
dvander@117
    30
  @property
dvander@117
    31
  def folder_name(self):
dvander@117
    32
    if not self.folder:
dvander@117
    33
      return ''
dvander@117
    34
    return self.folder
dvander@117
    35
dvander@86
    36
  def format(self):
dvander@86
    37
    text = ''
dvander@86
    38
    if self.type == nodetypes.Cxx:
dvander@86
    39
      return '[' + self.data['type'] + ']' + ' -> ' + (' '.join([arg for arg in self.data['argv']]))
dvander@115
    40
    if self.type == nodetypes.Symlink:
dvander@117
    41
      return 'ln -s "{0}" "{1}"'.format(self.data[0], os.path.join(self.folder_name, self.data[1]))
dvander@117
    42
    if self.type == nodetypes.Copy:
dvander@117
    43
      return 'cp "{0}" "{1}"'.format(self.data[0], os.path.join(self.folder_name, self.data[1]))
dvander@86
    44
    return (' '.join([arg for arg in self.data]))
dvander@86
    45
dvander@94
    46
class WorkerChild(ChildProcessListener):
dvander@142
    47
  def __init__(self, pump, channel, buildPath):
dvander@131
    48
    super(WorkerChild, self).__init__(pump, channel)
dvander@94
    49
    self.buildPath = buildPath
dvander@94
    50
    self.pid = os.getpid()
dvander@93
    51
    self.messageMap = {
dvander@93
    52
      'task': lambda channel, message: self.receiveTask(channel, message)
dvander@93
    53
    }
dvander@94
    54
    self.taskMap = {
dvander@94
    55
      'cxx': lambda message: self.doCompile(message),
dvander@115
    56
      'cmd': lambda message: self.doCommand(message),
dvander@117
    57
      'ln': lambda message: self.doSymlink(message),
dvander@117
    58
      'cp': lambda message: self.doCopy(message),
dvander@190
    59
      'rc': lambda message: self.doResource(message),
dvander@94
    60
    }
dvander@87
    61
dvander@131
    62
    self.channel.send({
dvander@131
    63
      'id': 'ready',
dvander@131
    64
      'finished': None
dvander@131
    65
    })
dvander@89
    66
dvander@93
    67
  def receiveTask(self, channel, message):
dvander@94
    68
    task_id = message['task_id']
dvander@94
    69
    task_type = message['task_type']
dvander@94
    70
    task_folder = message['task_folder']
dvander@94
    71
    if not task_folder:
dvander@94
    72
      task_folder = '.'
dvander@94
    73
dvander@94
    74
    # Remove all outputs.
dvander@94
    75
    for output in message['task_outputs']:
dvander@94
    76
      try:
dvander@94
    77
        os.unlink(output)
dvander@94
    78
      except OSError as exn:
dvander@94
    79
        if exn.errno != errno.ENOENT:
danderson@132
    80
          response = {
danderson@132
    81
            'ok': False,
danderson@132
    82
            'cmdline': 'rm {0}'.format(output),
danderson@132
    83
            'stdout': '',
danderson@132
    84
            'stderr': '{0}'.format(exn)
danderson@132
    85
          }
danderson@132
    86
          return self.issueResponse(message, response)
dvander@94
    87
dvander@124
    88
    if not message['task_folder']:
dvander@124
    89
      message['task_folder'] = '.'
dvander@124
    90
dvander@94
    91
    # Do the task.
dvander@94
    92
    response = self.taskMap[task_type](message)
danderson@132
    93
    return self.issueResponse(message, response)
dvander@94
    94
danderson@132
    95
  def issueResponse(self, message, response):
dvander@94
    96
    # Compute new timestamps for all command outputs.
dvander@94
    97
    new_timestamps = []
dvander@94
    98
    if response['ok']:
dvander@94
    99
      for output in message['task_outputs']:
dvander@185
   100
        try:
dvander@185
   101
          new_timestamps.append((output, os.path.getmtime(output)))
dvander@185
   102
        except:
dvander@185
   103
          response['ok'] = False
dvander@185
   104
          response['stderr'] += 'Expected output file, but not found: {0}'.format(output)
dvander@185
   105
          break
dvander@94
   106
dvander@94
   107
    # Send a message back to the master process to update the DAG and spew
dvander@94
   108
    # stdout/stderr if needed.
dvander@94
   109
    response['id'] = 'results'
dvander@94
   110
    response['pid'] = self.pid
danderson@132
   111
    response['task_id'] = message['task_id']
dvander@94
   112
    response['updates'] = new_timestamps
dvander@142
   113
    self.channel.send(response)
dvander@94
   114
dvander@94
   115
  def doCommand(self, message):
dvander@94
   116
    task_folder = message['task_folder']
dvander@130
   117
    argv = message['task_data']
dvander@94
   118
    with util.FolderChanger(task_folder):
danderson@133
   119
      try:
danderson@133
   120
        p, stdout, stderr = util.Execute(argv)
danderson@133
   121
        status = p.returncode == 0
danderson@133
   122
      except Exception as exn:
danderson@133
   123
        status = False
danderson@133
   124
        stdout = ''
danderson@133
   125
        stderr = '{0}'.format(exn)
dvander@94
   126
dvander@94
   127
    reply = {
danderson@133
   128
      'ok': status,
dvander@130
   129
      'cmdline': ' '.join([arg for arg in argv]),
dvander@94
   130
      'stdout': stdout,
dvander@115
   131
      'stderr': stderr,
dvander@115
   132
    }
dvander@115
   133
    return reply
dvander@115
   134
dvander@115
   135
  def doSymlink(self, message):
dvander@115
   136
    task_folder = message['task_folder']
dvander@115
   137
    source_path, output_path = message['task_data']
dvander@115
   138
dvander@115
   139
    with util.FolderChanger(task_folder):
dvander@168
   140
      rcode, stdout, stderr = util.symlink(source_path, output_path)
dvander@115
   141
dvander@115
   142
    reply = {
dvander@128
   143
      'ok': rcode == 0,
dvander@130
   144
      'cmdline': 'ln -s {0} {1}'.format(source_path, output_path),
dvander@130
   145
      'stdout': stdout,
dvander@128
   146
      'stderr': stderr,
dvander@117
   147
    }
dvander@117
   148
    return reply
dvander@117
   149
dvander@117
   150
  def doCopy(self, message):
dvander@117
   151
    task_folder = message['task_folder']
dvander@117
   152
    source_path, output_path = message['task_data']
dvander@117
   153
dvander@117
   154
    with util.FolderChanger(task_folder):
dvander@139
   155
      if os.path.exists(source_path):
dvander@139
   156
        shutil.copy(source_path, output_path)
dvander@139
   157
        ok = True
dvander@139
   158
        stderr = ''
dvander@139
   159
      else:
dvander@139
   160
        ok = False
dvander@139
   161
        stderr = 'File not found: {0}'.format(source_path)
dvander@117
   162
dvander@117
   163
    reply = {
dvander@139
   164
      'ok': ok,
dvander@131
   165
      'cmdline': 'cp "{0}" "{1}"'.format(source_path, os.path.join(task_folder, output_path)),
dvander@130
   166
      'stdout': '',
dvander@139
   167
      'stderr': stderr,
dvander@94
   168
    }
dvander@94
   169
    return reply
dvander@94
   170
dvander@190
   171
  # Adjusts any dependencies relative to the current folder, to be relative to
dvander@190
   172
  # the output folder instead.
dvander@190
   173
  def rewriteDeps(self, deps):
dvander@190
   174
    paths = []
dvander@190
   175
    for inc_path in deps:
dvander@190
   176
      if not os.path.isabs(inc_path):
dvander@190
   177
        inc_path = os.path.abspath(inc_path)
dvander@190
   178
dvander@190
   179
      # Detect whether the include is within the build folder or not.
dvander@190
   180
      build_path = self.buildPath
dvander@190
   181
      if build_path[-1] != '/':
dvander@190
   182
        build_path += '/'
dvander@190
   183
      prefix = os.path.commonprefix([build_path, inc_path])
dvander@190
   184
      if prefix == build_path:
dvander@190
   185
        # The include is not a system include, i.e. it was generated, so
dvander@190
   186
        # rewrite the path to be relative to the build folder.
dvander@190
   187
        inc_path = os.path.relpath(inc_path, self.buildPath)
dvander@190
   188
dvander@190
   189
      paths.append(inc_path)
dvander@190
   190
    return paths
dvander@190
   191
dvander@94
   192
  def doCompile(self, message):
dvander@94
   193
    task_folder = message['task_folder']
dvander@94
   194
    task_data = message['task_data']
dvander@94
   195
    cc_type = task_data['type']
dvander@94
   196
    argv = task_data['argv']
dvander@94
   197
dvander@94
   198
    with util.FolderChanger(task_folder):
dvander@94
   199
      p, out, err = util.Execute(argv)
dvander@94
   200
      if cc_type == 'gcc':
dvander@94
   201
        err, deps = util.ParseGCCDeps(err)
dvander@128
   202
      elif cc_type == 'msvc':
dvander@128
   203
        out, deps = util.ParseMSVCDeps(out)
dvander@183
   204
      elif cc_type == 'sun':
dvander@183
   205
        err, deps = util.ParseSunDeps(err)
dvander@94
   206
      else:
dvander@94
   207
        raise Exception('unknown compiler type')
dvander@100
   208
dvander@190
   209
      paths = self.rewriteDeps(deps)
dvander@94
   210
dvander@94
   211
    reply = {
dvander@94
   212
      'ok': p.returncode == 0,
dvander@130
   213
      'cmdline': ' '.join([arg for arg in argv]),
dvander@94
   214
      'stdout': out,
dvander@94
   215
      'stderr': err,
dvander@94
   216
      'deps': paths,
dvander@94
   217
    }
dvander@94
   218
    return reply
dvander@87
   219
dvander@190
   220
  def doResource(self, message):
dvander@190
   221
    task_folder = message['task_folder']
dvander@190
   222
    task_data = message['task_data']
dvander@190
   223
    cl_argv = task_data['cl_argv']
dvander@190
   224
    rc_argv = task_data['rc_argv']
dvander@190
   225
dvander@190
   226
    with util.FolderChanger(task_folder):
dvander@190
   227
      # Includes go to stderr when we preprocess to stdout.
dvander@204
   228
      p, out, err = util.Execute(cl_argv)
dvander@204
   229
      out, deps = util.ParseMSVCDeps(err)
dvander@190
   230
      paths = self.rewriteDeps(deps)
dvander@190
   231
dvander@190
   232
      if p.returncode == 0:
dvander@190
   233
        p, out, err = util.Execute(rc_argv)
dvander@190
   234
        
dvander@190
   235
    reply = {
dvander@190
   236
      'ok': p.returncode == 0,
dvander@199
   237
      'cmdline': (' '.join([arg for arg in cl_argv]) + ' && ' + ' '.join([arg for arg in rc_argv])),
dvander@190
   238
      'stdout': out,
dvander@190
   239
      'stderr': err,
dvander@190
   240
      'deps': paths,
dvander@190
   241
    }
dvander@190
   242
    return reply
dvander@190
   243
dvander@190
   244
dvander@92
   245
# The WorkerParent is in the same process as the TaskMasterChild.
dvander@94
   246
class WorkerParent(ParentProcessListener):
dvander@142
   247
  def __init__(self, taskMaster):
dvander@92
   248
    super(WorkerParent, self).__init__('Worker')
dvander@87
   249
    self.taskMaster = taskMaster
dvander@93
   250
    self.messageMap = {
dvander@94
   251
      'ready': lambda child, message: self.receiveReady(child, message),
dvander@142
   252
      'results': lambda child, message: self.receiveResults(child, message),
dvander@93
   253
    }
dvander@87
   254
dvander@142
   255
  def receiveReady(self, child, message):
dvander@142
   256
    self.taskMaster.onWorkerReady(child, message)
dvander@87
   257
dvander@142
   258
  def receiveResults(self, child, message):
dvander@142
   259
    self.taskMaster.onWorkerResults(child, message)
dvander@92
   260
dvander@88
   261
  def receiveError(self, child, error):
dvander@93
   262
    self.taskMaster.onWorkerDied(child, error)
dvander@88
   263
dvander@92
   264
# The TaskMasterChild is in the same process as the WorkerParent.
dvander@94
   265
class TaskMasterChild(ChildProcessListener):
dvander@142
   266
  def __init__(self, pump, channel, task_graph, buildPath, num_processes):
dvander@131
   267
    super(TaskMasterChild, self).__init__(pump, channel)
dvander@93
   268
    self.task_graph = task_graph
dvander@93
   269
    self.outstanding = {}
dvander@95
   270
    self.idle = set()
dvander@94
   271
    self.build_failed = False
dvander@100
   272
    self.build_completed = False
dvander@173
   273
    self.messageMap = {
dvander@173
   274
      'stop': lambda channel, message: self.receiveStop(channel, message)
dvander@173
   275
    }
dvander@93
   276
dvander@89
   277
    self.procman = ProcessManager(pump)
dvander@142
   278
    for i in range(num_processes):
dvander@94
   279
      self.procman.spawn(
dvander@142
   280
        WorkerParent(self),
dvander@94
   281
        WorkerChild,
dvander@142
   282
        args=(buildPath,)
dvander@94
   283
      )
dvander@94
   284
dvander@131
   285
    self.channel.send({
dvander@131
   286
      'id': 'spawned',
dvander@142
   287
      'pid': os.getpid(),
dvander@142
   288
      'type': 'taskmaster'
dvander@131
   289
    })
dvander@131
   290
dvander@173
   291
  def receiveStop(self, channel, message):
dvander@173
   292
    if not message['ok']:
dvander@174
   293
      self.terminateBuild()
dvander@173
   294
    self.close_idle()
dvander@173
   295
dvander@174
   296
  def terminateBuild(self):
dvander@173
   297
    if self.build_failed:
dvander@173
   298
      return
dvander@173
   299
dvander@173
   300
    self.build_failed = True
dvander@173
   301
    self.close_idle()
dvander@94
   302
dvander@94
   303
  def receiveClose(self, channel):
dvander@94
   304
    self.procman.shutdown()
dvander@173
   305
    self.pump.cancel()
dvander@87
   306
dvander@142
   307
  def onWorkerResults(self, child, message):
dvander@142
   308
    # Forward the results to the master process.
dvander@142
   309
    self.channel.send(message)
dvander@142
   310
dvander@94
   311
    if not message['ok']:
dvander@94
   312
      self.channel.send({
dvander@94
   313
        'id': 'completed',
dvander@94
   314
        'status': 'failed',
dvander@94
   315
      })
dvander@94
   316
      self.procman.close(child)
dvander@174
   317
      self.terminateBuild()
dvander@94
   318
      return
dvander@94
   319
dvander@94
   320
    task_id = message['task_id']
dvander@94
   321
    task, child = self.outstanding[task_id]
dvander@94
   322
    del self.outstanding[task_id]
dvander@94
   323
dvander@94
   324
    # Enqueue any tasks that can be run if this was their last outstanding
dvander@94
   325
    # dependency.
dvander@94
   326
    for outgoing in task.outgoing:
dvander@94
   327
      outgoing.incoming.remove(task)
dvander@94
   328
      if len(outgoing.incoming) == 0:
dvander@94
   329
        self.task_graph.append(outgoing)
dvander@94
   330
dvander@142
   331
    self.onWorkerReady(child, None)
dvander@89
   332
dvander@95
   333
    # If more stuff was queued, and we have idle processes, use them.
dvander@95
   334
    while len(self.task_graph) and len(self.idle):
dvander@95
   335
      child = self.idle.pop()
dvander@142
   336
      if not self.onWorkerReady(child, None):
dvander@95
   337
        break
dvander@95
   338
dvander@97
   339
  def close_idle(self):
dvander@97
   340
    for child in self.idle:
dvander@97
   341
      self.procman.close(child)
dvander@97
   342
    self.idle = set()
dvander@97
   343
dvander@142
   344
  def onWorkerReady(self, child, message):
dvander@142
   345
    if message and not message['finished']:
dvander@142
   346
      self.channel.send({
dvander@142
   347
        'id': 'spawned',
dvander@142
   348
        'pid': child.pid,
dvander@142
   349
        'type': 'worker'
dvander@142
   350
      })
dvander@142
   351
dvander@173
   352
    # If the build failed, ignore the message, and shutdown the process.
dvander@100
   353
    if self.build_failed:
dvander@173
   354
      self.procman.close(child)
dvander@173
   355
      self.maybe_request_shutdown(child)
dvander@173
   356
      return
dvander@100
   357
dvander@93
   358
    if not len(self.task_graph):
dvander@93
   359
      if len(self.outstanding):
dvander@93
   360
        # There are still tasks left to complete, but they're waiting on
dvander@93
   361
        # others to finish. Mark this process as ready and just ignore the
dvander@93
   362
        # status change for now.
dvander@95
   363
        self.idle.add(child)
dvander@93
   364
      else:
dvander@93
   365
        # There are no tasks remaining, the worker is not needed.
dvander@100
   366
        self.build_completed = True
dvander@93
   367
        self.procman.close(child)
dvander@97
   368
        self.close_idle()
dvander@97
   369
        self.channel.send({
dvander@97
   370
          'id': 'completed',
dvander@97
   371
          'status': 'ok'
dvander@97
   372
        })
dvander@95
   373
      return False
dvander@93
   374
dvander@93
   375
    # Send a task to the worker.
dvander@93
   376
    task = self.task_graph.pop()
dvander@93
   377
dvander@93
   378
    message = {
dvander@93
   379
      'id': 'task',
dvander@93
   380
      'task_id': task.id,
dvander@93
   381
      'task_type': task.type,
dvander@93
   382
      'task_data': task.data,
dvander@94
   383
      'task_folder': task.folder,
dvander@93
   384
      'task_outputs': task.outputs
dvander@93
   385
    }
dvander@93
   386
    child.send(message)
dvander@93
   387
    self.outstanding[task.id] = (task, child)
dvander@95
   388
    return True
dvander@93
   389
dvander@94
   390
  def onWorkerCrashed(self, child, task):
dvander@94
   391
    self.channel.send({
dvander@94
   392
      'id': 'completed',
dvander@94
   393
      'status': 'crashed',
dvander@94
   394
      'task_id': task.id,
dvander@94
   395
    })
dvander@174
   396
    self.terminateBuild()
dvander@94
   397
dvander@93
   398
  def onWorkerDied(self, child, error):
dvander@93
   399
    if error != Error.NormalShutdown:
dvander@93
   400
      for task_id in self.outstanding.keys():
dvander@94
   401
        task, task_child = self.outstanding[task_id]
dvander@94
   402
        if task_child == child:
dvander@94
   403
          # A worker failed, but crashed, so we have to tell the main process.
dvander@94
   404
          self.onWorkerCrashed(child, task)
dvander@94
   405
          break
dvander@93
   406
dvander@95
   407
    self.idle.discard(child)
dvander@173
   408
    self.maybe_request_shutdown(child)
dvander@94
   409
dvander@173
   410
  def maybe_request_shutdown(self, child):
dvander@94
   411
    for other_child in self.procman.children:
dvander@94
   412
      if other_child == child:
dvander@94
   413
        continue
dvander@94
   414
      if other_child.is_alive():
dvander@94
   415
        return
dvander@94
   416
dvander@173
   417
    # If we got here, no other child processes are live, so we can ask for
dvander@173
   418
    # safe shutdown. This is needed to make sure all our messages arrive,
dvander@173
   419
    # since closing one end of the pipe destroys any leftover data.
dvander@173
   420
    self.channel.send({
dvander@173
   421
      'id': 'done'
dvander@173
   422
    })
dvander@89
   423
dvander@94
   424
class TaskMasterParent(ParentProcessListener):
dvander@98
   425
  def __init__(self, cx, builder, task_graph, max_parallel):
dvander@92
   426
    super(TaskMasterParent, self).__init__('TaskMaster')
dvander@86
   427
    self.cx = cx
dvander@94
   428
    self.builder = builder
dvander@94
   429
    self.build_failed = False
dvander@94
   430
    self.messageMap = {
dvander@131
   431
      'completed': lambda child, message: self.receiveCompleted(child, message),
dvander@131
   432
      'spawned': lambda child, message: self.receiveSpawned(message),
dvander@173
   433
      'results': lambda child, message: self.processResults(message),
dvander@173
   434
      'done': lambda child, message: self.receiveDone(message),
dvander@94
   435
    }
dvander@87
   436
dvander@87
   437
    # Figure out how many tasks to create.
dvander@87
   438
    if cx.options.jobs == 0:
dvander@87
   439
      # Using 1 process will be strictly worse than an in-process build,
dvander@87
   440
      # since we incur the additional overhead of message passing. Instead,
dvander@87
   441
      # we use two processes as the minimal number. If that turns out to be
dvander@87
   442
      # bad we can create an in-process TaskMaster later.
dvander@91
   443
      if mp.cpu_count() == 1:
dvander@91
   444
        num_processes = 2
dvander@91
   445
      else:
dvander@152
   446
        num_processes = int(mp.cpu_count() * 1.25)
dvander@99
   447
    else:
dvander@99
   448
      num_processes = cx.options.jobs
dvander@87
   449
dvander@87
   450
    # Don't create more processes than we'll need.
dvander@98
   451
    if num_processes > max_parallel:
dvander@98
   452
      num_processes = max_parallel
dvander@94
   453
dvander@87
   454
    # Spawn the task master.
dvander@173
   455
    self.taskMaster = cx.procman.spawn(
dvander@94
   456
      self,
dvander@94
   457
      TaskMasterChild,
dvander@142
   458
      args=(task_graph, cx.buildPath, num_processes)
dvander@94
   459
    )
dvander@87
   460
dvander@173
   461
  def receiveDone(self, message):
dvander@173
   462
    self.cx.procman.shutdown()
dvander@173
   463
dvander@94
   464
  def processResults(self, message):
dvander@130
   465
    if message['ok']:
dvander@130
   466
      color = util.ConsoleGreen
dvander@130
   467
    else:
dvander@130
   468
      color = util.ConsoleRed
dvander@130
   469
    util.con_out(
dvander@130
   470
      util.ConsoleBlue,
dvander@130
   471
      '[{0}]'.format(message['pid']),
dvander@130
   472
      util.ConsoleNormal,
dvander@130
   473
      ' ',
dvander@130
   474
      color,
dvander@130
   475
      message['cmdline'],
dvander@130
   476
      util.ConsoleNormal
dvander@130
   477
    )
dvander@94
   478
    if len(message['stdout']):
dvander@130
   479
      sys.stdout.write(message['stdout'])
dvander@128
   480
      if message['stdout'][-1] != '\n':
dvander@128
   481
        sys.stdout.write('\n')
dvander@94
   482
    if len(message['stderr']):
dvander@94
   483
      sys.stderr.write(message['stderr'])
dvander@128
   484
      if message['stderr'][-1] != '\n':
dvander@128
   485
        sys.stderr.write('\n')
dvander@94
   486
dvander@94
   487
    if not message['ok']:
dvander@173
   488
      self.terminateBuild()
dvander@94
   489
      return
dvander@94
   490
dvander@94
   491
    task_id = message['task_id']
dvander@94
   492
    updates = message['updates']
dvander@141
   493
    if not self.builder.updateGraph(task_id, updates, message):
dvander@130
   494
      util.con_out(
dvander@130
   495
        util.ConsoleRed,
dvander@130
   496
        'Failed to update node!',
dvander@130
   497
        util.ConsoleNormal
dvander@130
   498
      )
dvander@173
   499
      self.terminateBuild()
dvander@94
   500
dvander@173
   501
  def terminateBuild(self):
dvander@174
   502
    if self.build_failed:
dvander@174
   503
      return
dvander@174
   504
    self.build_failed = True
dvander@174
   505
    self.taskMaster.send({
dvander@174
   506
      'id': 'stop',
dvander@174
   507
      'ok': False
dvander@174
   508
    })
dvander@94
   509
dvander@131
   510
  def receiveSpawned(self, message):
dvander@131
   511
    util.con_out(
dvander@131
   512
      util.ConsoleHeader,
dvander@142
   513
      'Spawned {0} (pid: {1})'.format(message['type'], message['pid']),
dvander@131
   514
      util.ConsoleNormal
dvander@131
   515
    )
dvander@131
   516
dvander@88
   517
  def receiveError(self, child, error):
dvander@94
   518
    if error != Error.NormalShutdown:
dvander@128
   519
      sys.stderr.write('Received unexpected error from child process {0}: {1}\n'.format(child.pid, error))
dvander@173
   520
      self.terminateBuild()
dvander@94
   521
dvander@94
   522
  def receiveCompleted(self, child, message):
dvander@94
   523
    if message['status'] == 'crashed':
dvander@142
   524
      task = self.builder.commands[message['task_id']]
dvander@94
   525
      sys.stderr.write('Crashed trying to perform update:\n')
dvander@94
   526
      sys.stderr.write('  : {0}\n'.format(task.entry.format()))
dvander@173
   527
      self.terminateBuild()
dvander@94
   528
    elif message['status'] == 'failed':
dvander@173
   529
      self.terminateBuild()
dvander@94
   530
    else:
dvander@174
   531
      self.taskMaster.send({
dvander@174
   532
        'id': 'stop',
dvander@174
   533
        'ok': True
dvander@174
   534
      })
dvander@88
   535
dvander@87
   536
  def run(self):
dvander@87
   537
    self.cx.messagePump.pump()
dvander@94
   538
    return not self.build_failed