mirror of
https://github.com/golang/go
synced 2024-11-18 11:04:42 -07:00
2ce13ba4b0
R=agl1 CC=golang-dev https://golang.org/cl/194116
242 lines
7.4 KiB
Python
242 lines
7.4 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright 2009 The Go Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style
|
|
# license that can be found in the LICENSE file.
|
|
|
|
# This is a utility script for implementing a Go build slave.
|
|
|
|
import binascii
|
|
import httplib
|
|
import os
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
buildhost = ''
|
|
buildport = -1
|
|
buildkey = ''
|
|
|
|
def main(args):
|
|
global buildport, buildhost, buildkey
|
|
|
|
if len(args) < 2:
|
|
return usage(args[0])
|
|
|
|
if 'BUILDHOST' not in os.environ:
|
|
print >>sys.stderr, "Please set $BUILDHOST"
|
|
return
|
|
buildhost = os.environ['BUILDHOST']
|
|
|
|
if 'BUILDPORT' not in os.environ:
|
|
buildport = 80
|
|
else:
|
|
buildport = int(os.environ['BUILDPORT'])
|
|
|
|
try:
|
|
buildkey = file('%s/.gobuildkey-%s' % (os.environ['HOME'], os.environ['BUILDER']), 'r').read().strip()
|
|
except IOError:
|
|
try:
|
|
buildkey = file('%s/.gobuildkey' % os.environ['HOME'], 'r').read().strip()
|
|
except IOError:
|
|
print >>sys.stderr, "Need key in ~/.gobuildkey-%s or ~/.gobuildkey" % os.environ['BUILDER']
|
|
return
|
|
|
|
if args[1] == 'init':
|
|
return doInit(args)
|
|
elif args[1] == 'hwget':
|
|
return doHWGet(args)
|
|
elif args[1] == 'hwset':
|
|
return doHWSet(args)
|
|
elif args[1] == 'next':
|
|
return doNext(args)
|
|
elif args[1] == 'record':
|
|
return doRecord(args)
|
|
elif args[1] == 'benchmarks':
|
|
return doBenchmarks(args)
|
|
else:
|
|
return usage(args[0])
|
|
|
|
def usage(name):
|
|
sys.stderr.write('''Usage: %s <command>
|
|
|
|
Commands:
|
|
init <rev>: init the build bot with the given commit as the first in history
|
|
hwget <builder>: get the most recent revision built by the given builder
|
|
hwset <builder> <rev>: get the most recent revision built by the given builder
|
|
next <builder>: get the next revision number to by built by the given builder
|
|
record <builder> <rev> <ok|log file>: record a build result
|
|
benchmarks <builder> <rev> <log file>: record benchmark numbers
|
|
''' % name)
|
|
return 1
|
|
|
|
def doInit(args):
|
|
if len(args) != 3:
|
|
return usage(args[0])
|
|
c = getCommit(args[2])
|
|
if c is None:
|
|
fatal('Cannot get commit %s' % args[2])
|
|
|
|
return command('init', {'node': c.node, 'date': c.date, 'user': c.user, 'desc': c.desc})
|
|
|
|
def doHWGet(args, retries = 0):
|
|
if len(args) != 3:
|
|
return usage(args[0])
|
|
conn = httplib.HTTPConnection(buildhost, buildport, True)
|
|
conn.request('GET', '/hw-get?builder=%s' % args[2]);
|
|
reply = conn.getresponse()
|
|
if reply.status == 200:
|
|
print reply.read()
|
|
elif reply.status == 500 and retries < 3:
|
|
time.sleep(3)
|
|
return doHWGet(args, retries = retries + 1)
|
|
else:
|
|
raise Failed('get-hw returned %d' % reply.status)
|
|
return 0
|
|
|
|
def doHWSet(args):
|
|
if len(args) != 4:
|
|
return usage(args[0])
|
|
c = getCommit(args[3])
|
|
if c is None:
|
|
fatal('Cannot get commit %s' % args[3])
|
|
|
|
return command('hw-set', {'builder': args[2], 'hw': c.node})
|
|
|
|
def doNext(args):
|
|
if len(args) != 3:
|
|
return usage(args[0])
|
|
conn = httplib.HTTPConnection(buildhost, buildport, True)
|
|
conn.request('GET', '/hw-get?builder=%s' % args[2]);
|
|
reply = conn.getresponse()
|
|
if reply.status == 200:
|
|
rev = reply.read()
|
|
else:
|
|
raise Failed('get-hw returned %d' % reply.status)
|
|
|
|
c = getCommit(rev)
|
|
next = getCommit(str(c.num + 1))
|
|
if next is not None and next.parent == c.node:
|
|
print c.num + 1
|
|
else:
|
|
print "<none>"
|
|
return 0
|
|
|
|
def doRecord(args):
|
|
if len(args) != 5:
|
|
return usage(args[0])
|
|
builder = args[2]
|
|
rev = args[3]
|
|
c = getCommit(rev)
|
|
if c is None:
|
|
print >>sys.stderr, "Bad revision:", rev
|
|
return 1
|
|
logfile = args[4]
|
|
log = ''
|
|
if logfile != 'ok':
|
|
log = file(logfile, 'r').read()
|
|
return command('build', {'node': c.node, 'parent': c.parent, 'date': c.date, 'user': c.user, 'desc': c.desc, 'log': log, 'builder': builder})
|
|
|
|
def doBenchmarks(args):
|
|
if len(args) != 5:
|
|
return usage(args[0])
|
|
builder = args[2]
|
|
rev = args[3]
|
|
c = getCommit(rev)
|
|
if c is None:
|
|
print >>sys.stderr, "Bad revision:", rev
|
|
return 1
|
|
|
|
benchmarks = {}
|
|
for line in file(args[4], 'r').readlines():
|
|
if 'Benchmark' in line and 'ns/op' in line:
|
|
parts = line.split()
|
|
if parts[3] == 'ns/op':
|
|
benchmarks[parts[0]] = (parts[1], parts[2])
|
|
|
|
e = []
|
|
for (name, (a, b)) in benchmarks.items():
|
|
e.append(struct.pack('>H', len(name)))
|
|
e.append(name)
|
|
e.append(struct.pack('>H', len(a)))
|
|
e.append(a)
|
|
e.append(struct.pack('>H', len(b)))
|
|
e.append(b)
|
|
return command('benchmarks', {'node': c.node, 'builder': builder, 'benchmarkdata': binascii.b2a_base64(''.join(e))})
|
|
|
|
def encodeMultipartFormdata(fields, files):
|
|
"""fields is a sequence of (name, value) elements for regular form fields.
|
|
files is a sequence of (name, filename, value) elements for data to be uploaded as files"""
|
|
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
|
|
CRLF = '\r\n'
|
|
L = []
|
|
for (key, value) in fields.items():
|
|
L.append('--' + BOUNDARY)
|
|
L.append('Content-Disposition: form-data; name="%s"' % key)
|
|
L.append('')
|
|
L.append(value)
|
|
for (key, filename, value) in files:
|
|
L.append('--' + BOUNDARY)
|
|
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
|
|
L.append('Content-Type: %s' % get_content_type(filename))
|
|
L.append('')
|
|
L.append(value)
|
|
L.append('--' + BOUNDARY + '--')
|
|
L.append('')
|
|
body = CRLF.join(L)
|
|
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
|
|
return content_type, body
|
|
|
|
def unescapeXML(s):
|
|
return s.replace('<', '<').replace('>', '>').replace('&', '&')
|
|
|
|
class Commit:
|
|
pass
|
|
|
|
def getCommit(rev):
|
|
output, stderr = subprocess.Popen(['hg', 'log', '-r', rev, '-l', '1', '--template', '{rev}>{node|escape}>{author|escape}>{date}>{desc}'], stdout = subprocess.PIPE, stderr = subprocess.PIPE, close_fds = True).communicate()
|
|
if len(stderr) > 0:
|
|
return None
|
|
[n, node, user, date, desc] = output.split('>', 4)
|
|
|
|
c = Commit()
|
|
c.num = int(n)
|
|
c.node = unescapeXML(node)
|
|
c.user = unescapeXML(user)
|
|
c.date = unescapeXML(date)
|
|
c.desc = desc
|
|
c.parent = ''
|
|
|
|
if c.num > 0:
|
|
output, _ = subprocess.Popen(['hg', 'log', '-r', str(c.num - 1), '-l', '1', '--template', '{node}'], stdout = subprocess.PIPE, close_fds = True).communicate()
|
|
c.parent = output
|
|
|
|
return c
|
|
|
|
class Failed(Exception):
|
|
def __init__(self, msg):
|
|
self.msg = msg
|
|
def __str__(self):
|
|
return self.msg
|
|
|
|
def command(cmd, args, retries = 0):
|
|
args['key'] = buildkey
|
|
contentType, body = encodeMultipartFormdata(args, [])
|
|
print body
|
|
conn = httplib.HTTPConnection(buildhost, buildport, True)
|
|
conn.request('POST', '/' + cmd, body, {'Content-Type': contentType})
|
|
reply = conn.getresponse()
|
|
if reply.status != 200:
|
|
print "Command failed. Output:"
|
|
print reply.read()
|
|
if reply.status == 500 and retries < 3:
|
|
print "Was a 500. Waiting two seconds and trying again."
|
|
time.sleep(2)
|
|
return command(cmd, args, retries = retries + 1)
|
|
if reply.status != 200:
|
|
raise Failed('Command "%s" returned %d' % (cmd, reply.status))
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|