blob: ee6c6fbe73ff3fb5db69ba0c8d108759fe50a0b2 [file] [log] [blame]
import buildbot
import json
import os
import StringIO
from buildbot.steps.master import MasterShellCommand
from buildbot.steps.shell import SetProperty
from buildbot.steps.shell import WithProperties
from buildbot.steps.trigger import Trigger
from datetime import datetime, date, time
import zorg
import config
class NamedTrigger(Trigger):
"""Trigger subclass which allows overriding the trigger name, and also
allows attaching a log to link to the triggered builds."""
def __init__(self, name, triggeredBuilders = [], **kwargs):
Trigger.__init__(self, **kwargs)
self.name = name
self.triggeredBuilders = triggeredBuilders
self.addFactoryArguments(name = name,
triggeredBuilders = triggeredBuilders)
def start(self):
# Add a log linking to the triggered builders, if supplied.
if self.triggeredBuilders:
logText = StringIO.StringIO()
for builder in self.triggeredBuilders:
print >>logText, ('<b><a href="../../../../../../%s">%s'
'</a></b><br>' % (builder, builder))
self.addHTMLLog('triggered builds', str(logText.getvalue()))
# Dispatch to the super class.
Trigger.start(self)
def _project_from_name(buildname):
name = buildname.replace('llvm-gcc', 'llvm$gcc')\
.replace('apple-clang', 'apple$clang')
params = name.split('-')
project = params[0].replace('$', '-')
return project
def _determine_remote_file(props):
return os.path.join(os.getcwd(),props['scheduler'] + '_changes.txt')
def _load_changelist(props):
changelist = []
try:
for line in open(_determine_remote_file(props)).readlines():
change = json.loads(line)
change['category'] = props['next_phase']
if not change in changelist:
changelist.append(change)
except IOError:
pass
return json.dumps(changelist)
def _extract_changelist(status, stdin, stdout):
newProps = {}
changelist = []
if status:
return {'changes' : changelist, 'status' : status}
buildprops = json.loads(stdin)
props = buildprops['properties']
ss = buildprops['sourcestamp']
changes = ss['changes']
changelist = json.loads(_load_changelist(props))
for change in changes:
newchange={}
newchange['revision'] = change['revision']
if change.has_key('who'):
newchange['author'] = change['who']
else:
newchange['author'] = change['author']
changefiles = change['files']
files = []
for file in changefiles:
files.append(file['name'])
newchange['files'] = files
newchange['comments'] = change['comments']
# FIXME: not correct
# newchange['url'] = change['repository']
newchange['branch'] = change['branch']
newchange['link'] = change['revlink']
newchange['timestamp'] = change['when']
newchange['properties'] = {'phase_id' : props['phase_id']}
with open(_determine_remote_file(props), 'a+') as myfile:
myfile.write(json.dumps(newchange)+'\n')
if not newchange in changelist:
changelist.append(newchange)
changelist = sorted(changelist, key=lambda k: k['timestamp'])
newProps['changes'] = changelist
newProps['old_changes'] = changes
return newProps
def setProperty(f, new_property, new_value):
f.addStep(SetProperty(name = 'set.' + new_property,
command=['echo', new_value],
property=new_property,
description=['set property', new_property],
workdir='.'))
return f
def getBuildDir(f):
f.addStep(SetProperty(name='get.build.dir',
command=['pwd'],
property='builddir',
description='set build dir',
workdir='.'))
return f
def getUserDir(f):
f.addStep(SetProperty(command=['sh', '-c', 'cd ~;pwd'],
haltOnFailure=True,
property='user_dir',
description=['set property', 'user_dir'],
workdir='.'))
return f
def GetLatestValidated(f):
import zorg.buildbot.util.artifacts as artifacts
f.addStep(buildbot.steps.shell.ShellCommand(
name='rm.host-compiler',
command=['rm', '-rfv', 'host-compiler', 'host-compiler.tar.gz'],
haltOnFailure=False, description=['rm', 'host-compiler'],
workdir=WithProperties('%(builddir)s')))
latest_url = artifacts.base_download_url
latest_url += '/validated_builds/clang-x86_64-darwin11-R.tar.gz'
f.addStep(buildbot.steps.shell.ShellCommand(
name='download.artifacts',
command=['curl', '-fvLo', 'host-compiler.tar.gz', latest_url],
haltOnFailure=True, description=['download build artifacts'],
workdir=WithProperties('%(builddir)s')))
f.addStep(buildbot.steps.shell.ShellCommand(
name='unzip', command=['tar', '-zxvf','../host-compiler.tar.gz'],
haltOnFailure=True, description=['extract', 'host-compiler'],
workdir='host-compiler'))
return f
def find_cc(status, stdin, stdout):
lines = filter(bool, stdin.split('\n'))
for line in lines:
if 'bin/clang' in line:
cc_path = line
return { 'cc_path' : cc_path }
return {}
def find_cxx(status, stdin, stdout):
lines = filter(bool, stdin.split('\n'))
for line in lines:
if 'bin/clang++' in line:
cxx_path = line
return { 'cxx_path' : cxx_path }
return {}
def find_liblto(status, stdin, stdout):
lines = filter(bool, stdin.split('\n'))
for line in lines:
if 'lib/libLTO.dylib' in line:
liblto_path = os.path.dirname(line)
return { 'liblto_path' : liblto_path }
return {}
def determine_phase_id(props):
# phase_id should be generated by the first phase to run and copied as a
# propery to downstream builds
if props.has_key('phase_id'):
return props['phase_id']
else:
timestamp = datetime.now()
timestamp = timestamp.strftime('%Y%m%d_%H%M%S')
phase_id = 'r' + str(props['revision'])
phase_id += '-t' + timestamp
phase_id += '-b' + str(props['buildnumber'])
return phase_id
def getPhaseBuilderFactory(config, phase, next_phase, stages):
from buildbot.steps.transfer import JSONPropertiesDownload
# Create the build factory.
f = buildbot.process.factory.BuildFactory()
f.addStep(buildbot.steps.shell.ShellCommand(
command=['echo', WithProperties('%(phase_id:-)s')]))
# constuct a new phase_id if phase_id is not already set
phaseid = WithProperties('%(get_phase_id)s',
get_phase_id = determine_phase_id)
setProperty(f, 'phase_id', phaseid)
setProperty(f, 'next_phase', next_phase)
f.addStep(JSONPropertiesDownload(slavedest='build-properties.json'))
f.addStep(buildbot.steps.shell.SetProperty(
name = 'get.build.properties',
command = ['cat', 'build-properties.json'],
extract_fn = _extract_changelist))
# Buildbot uses got_revision instead of revision to identify builds.
# We set it below so that the revision shows up in the html status pages.
setProperty(f, 'got_revision', WithProperties('%(revision)s'))
# this generates URLs we can use to link back to the builder which
# triggered downstream builds
master_url = set_config_option('Master Options', 'master_url',
'http://localhost')
this_str = '/'.join([master_url, 'builders', '%(buildername)s', 'builds',
'%(buildnumber)s'])
setProperty(f, 'trigger', WithProperties(this_str))
# Properties we always copy...
copy_properties = [ 'phase_id', 'revision', 'got_revision', 'trigger' ]
# Add the trigger for the next phase.
changes = WithProperties('%(forward_changes)s',
forward_changes = _load_changelist)
# Add the triggers for each stage...
for i, (normal, experimental) in enumerate(stages):
# Add the experimental trigger, if used, but don't wait or fail for it.
if experimental:
scheduler = 'phase%d-stage%d-experimental' % (phase['number'], i)
f.addStep(Trigger(name = 'trigger.%s' % scheduler,
schedulerNames = [scheduler],
waitForFinish = False,
updateSourceStamp = False,
set_properties = {
'triggeredBuilders' : [b['name']
for b in normal],
},
copy_properties = copy_properties))
# Add the normal build trigger, if used.
if normal:
scheduler = 'phase%d-stage%d' % (phase['number'], i)
f.addStep(Trigger(name = 'trigger.%s' % scheduler,
schedulerNames = [scheduler],
waitForFinish = True, haltOnFailure = True,
updateSourceStamp = False,
set_properties = {
'triggeredBuilders' : [b['name']
for b in normal],
},
copy_properties = copy_properties))
f.addStep(MasterShellCommand(
name='trigger.next_phase', haltOnFailure = True,
command = ['./process_changelist.py', next_phase,
WithProperties('%(scheduler)s_changes.txt')],
description = ['Trigger', next_phase],
descriptionDone = ['Trigger', next_phase]))
# We have successfully sent the changes to the next phase, so it is now
# safe to erase the file and 'forget' the changes passed to this phase to
# date.
f.addStep(MasterShellCommand(
name='clear.changelist', haltOnFailure = True,
command = ['rm', '-fv', WithProperties('%(scheduler)s_changes.txt')],
description = ['Clear changelist'],
descriptionDone = ['Clear changelist']))
return f
def set_config_option(section, option, default=False):
import warnings
if config.options.has_option(section, option):
return config.options.get(section, option)
else:
warn_str = 'Please add the "%s" option to the ' % option
warn_str += '"%s" section of your local.cfg file' % section
warnings.warn(warn_str)
return default
def PublishGoodBuild(f=None, validated_build_dir='validated_builds',
publish_only_latest=False):
import config.phase_config
reload(config.phase_config)
artifacts_dir = set_config_option('Master Options', 'artifacts_path',
os.path.expanduser('~/artifacts/'))
# TODO: Add steps to prepare a release and announce a good build.
if f is None:
f = buildbot.process.factory.BuildFactory()
# Buildbot uses got_revision instead of revision to identify builds.
# We set it below so that the revision shows up in the html status pages.
setProperty(f, 'got_revision', WithProperties('%(revision)s'))
f.addStep(MasterShellCommand(
name='create.dir.validated_build_dir',
command = ['mkdir', '-p',
os.path.join(artifacts_dir, validated_build_dir)],
haltOnFailure = True,
description = ['create', validated_build_dir, 'dir']))
for phase in config.phase_config.phases:
for build in phase['builders']:
buildname = build['name']
project = _project_from_name(buildname)
if project in ('clang', 'llvm-gcc', 'apple-clang'):
file_str = project + '-%(get_phase_id)s.tar.gz'
link_str = os.path.join(artifacts_dir, buildname,
file_str)
build_artifacts_dir = os.path.join(artifacts_dir, validated_build_dir,
buildname)
f.addStep(MasterShellCommand(
name='Publish.Latest.' + buildname,
haltOnFailure=True,
command=['cp', '-v',
WithProperties(link_str,
get_phase_id=determine_phase_id),
os.path.join(artifacts_dir, validated_build_dir,
"%s.tar.gz" % buildname)],
description=['publish', buildname, 'as latest']))
if not publish_only_latest:
f.addStep(MasterShellCommand(
name='create.dir.%s' % buildname,
command = ['mkdir', '-p',
build_artifacts_dir],
haltOnFailure = True,
description = ['create', 'validated', 'dir', 'for',
buildname]))
artifacts_str = os.path.join(artifacts_dir, validated_build_dir,
buildname, file_str)
f.addStep(MasterShellCommand(
name='Publish.'+ buildname, haltOnFailure = True,
command = ['cp', '-v',
WithProperties(link_str,
get_phase_id=determine_phase_id),
WithProperties(artifacts_str,
get_phase_id=determine_phase_id)],
description = ['publish', buildname]))
return f
def SVNCleanupStep(f, name):
f.addStep(buildbot.steps.shell.ShellCommand(
name='svn.cleanup.%s' % name,
command=['svn', 'cleanup'],
haltOnFailure=False, flunkOnFailure=False,
description='svn cleanup %s just in case' % name,
workdir=name))
return f