bhearsum%mozilla.com b2520e2013 bustage fix for 448014 - use urljoin to create the hg.m.o url, because mercurial can't handle extra slasheys in the path
git-svn-id: svn://10.0.0.236/trunk@253671 18797224-902f-48f8-a5cc-f745e15eee43
2008-08-18 13:16:28 +00:00

392 lines
14 KiB
Python

# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Mozilla-specific Buildbot steps.
#
# The Initial Developer of the Original Code is
# Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Ben Hearsum <bhearsum@mozilla.com>
# ***** END LICENSE BLOCK *****
import os
from os import path, chmod
from time import localtime, strftime
import re
from urlparse import urljoin
from twisted.python import log
from twisted.internet import reactor
from buildbot.steps.shell import ShellCommand
from buildbot.steps.source import Mercurial
from buildbot.steps.transfer import FileDownload
from buildbot.process.buildstep import BuildStep
from buildbot.sourcestamp import SourceStamp
from buildbot.buildset import BuildSet
from buildbot.status.builder import SUCCESS, SKIPPED, WARNINGS
def parseSendchangeArguments(args):
"""This function parses the arguments that the Buildbot patch uploader
sends to Buildbot via the "changed files". It takes an argument of a
list of files and returns a dictionary with key/value pairs
"""
parsedArgs = {}
for arg in args:
try:
(key, value) = arg.split(":", 1)
value = value.lstrip().rstrip()
parsedArgs[key] = value
except:
pass
return parsedArgs
class MozillaTryProcessing(BuildStep):
warnOnFailure = True
name = "try server pre-processing"
"""This step does some preprocessing that the try server needs.
1) Resubmits any extra changes attached to this Build.
2) Sets all of the sendchange arguments as build properties
3) Provides a short header to tho build to help easily identify it
"""
def start(self):
changes = self.step_status.build.getChanges()
# 1) Resubmit extra changes
if len(changes) > 1:
builderName = self.step_status.build.builder.name
remainingChanges = changes[1:] # everything but the first
# get rid of the rest of the changes in the Build and BuildStatus
changes = changes[:1] # only the first one
self.step_status.build.changes = changes
bs = BuildSet([builderName], SourceStamp(changes=remainingChanges))
# submit the buildset back to the BuildMaster
self.build.builder.botmaster.parent.submitBuildSet(bs)
# 2) Set sendchange arguments to build properties
args = parseSendchangeArguments(changes[0].files)
# because we abuse 'branch' for triggering different builders
# we need to special case them, so we don't try to actually pull from
# PATCH_TRY or HG_TRY
branch = self.getProperty('branch')
if branch != 'PATCH_TRY' and branch != 'HG_TRY':
args['identifier'] = '%s-%s' % (branch, changes[0].revision[0:11])
for arg in args:
self.setProperty(arg, args[arg])
# 3) Add a header
buildNum = self.step_status.build.getNumber()
who = changes[0].who
comments = changes[0].comments
msg = "TinderboxPrint: %s\n" % who
if 'identifier' in args:
msg += "TinderboxPrint: %s\n" % args['identifier']
msg += "Comments: %s\n\n" % comments
self.addCompleteLog("header", msg)
self.finished(SUCCESS)
return SUCCESS
class MozillaDownloadMozconfig(FileDownload):
haltOnFailure = False
flunkOnFailure = False
warnOnFailure = False
def __init__(self, mastersrc=None, patchDir=".", **kwargs):
"""arguments:
@type patchDir: string
@param patchDir: The directory on the master that holds the mozconfig
This directory is relative to the base buildmaster
directory.
ie. /home/buildmaster/project
Defaults to '.'
"""
self.workdir = "mozilla/"
kwargs['workdir'] = "mozilla/"
self.patchDir = patchDir
# masterscr and slavedest get overridden in start()
FileDownload.__init__(self, mastersrc=mastersrc, slavedest=".mozconfig",
**kwargs)
def start(self):
changes = self.step_status.build.getChanges()
args = parseSendchangeArguments(changes[0].files)
# if we were passed in a mozconfig and also have an uploaded one
# they need to be combined, with the uploaded one overwriting any
# settings set by the passed in one
try:
uploadedFile = path.join(self.patchDir, args['mozconfig'])
os.stat(uploadedFile)
oldMasterSrc = self.mastersrc
self.mastersrc = uploadedFile
try:
os.stat(oldMasterSrc)
# we have both a passed in and uploaded mozconfig
self.mastersrc = "%s-%s" % (uploadedFile,
self.getProperty("slavename"))
# read in both configs
initialConfig = open(oldMasterSrc)
newConfig = initialConfig.read()
initialConfig.close()
uploadedConfig = open(uploadedFile)
newConfig += "\n"
newConfig += uploadedConfig.read()
uploadedConfig.close()
# now write out the whole new thing
mozconfig = open(self.mastersrc, "w")
mozconfig.write(newConfig)
mozconfig.close()
except (OSError, TypeError, KeyError):
# no passed in mozconfig, mastersrc set above
try:
os.stat(self.mastersrc)
except (OSError, TypeError, KeyError):
return SKIPPED
except (OSError, TypeError, KeyError):
# no uploaded mozconfig
try:
os.stat(self.mastersrc)
# if this succeeds, the passed in mastersrc is valid
except (OSError, TypeError, KeyError):
# nothing to transfer, skip
return SKIPPED
# everything is set up, download the file
FileDownload.start(self)
class MozillaPatchDownload(FileDownload):
"""This step reads a Change for a filename and downloads it to the slave.
It is typically used in conjunction with the MozillaCustomPatch step.
"""
haltOnFailure = True
def __init__(self, isOptional=False, patchDir=".", **kwargs):
"""arguments:
@type patchDir: string
@param patchDir: The directory on the master that holds the patches
This directory is relative to the base buildmaster
directory.
ie. /home/buildmaster/project
Defaults to '.'
'workdir' is assumed to be 'build' and should be passed if it is
anything else.
'isOptional' is assumed to be False; if the patch is optional, pass True.
"""
self.patchDir = patchDir
self.isOptional = isOptional
# mastersrc and slavedest get overridden in start()
if not 'workdir' in kwargs:
kwargs['workdir'] = "build"
FileDownload.__init__(self, mastersrc=".", slavedest=".", **kwargs)
def start(self):
changes = self.step_status.build.getChanges()
if len(changes) < 1:
return SKIPPED
args = parseSendchangeArguments(changes[0].files)
if not 'patchFile' in args and self.isOptional:
return SKIPPED
self.mastersrc = "%s/%s" % (self.patchDir, args['patchFile'])
self.slavedest = "%s" % (args['patchFile'])
# now that everything is set-up, download the file
FileDownload.start(self)
class MozillaUploadTryBuild(ShellCommand):
warnOnFailure = True
def __init__(self, slavedir, baseFilename, scpString, **kwargs):
"""
@type slavedir: string
@param slavedir: The directory that contains the file that will be
transferred (on the BuildSlave)
@type baseFilename: string
@param baseFilename: The filename (without the identifier) of the file
that will be transferred
@type scpString: string
@param scpString: The scp user@host:/dir string to upload the file to.
For example,
foo@some.server.com:/var/www.
This user should have passwordless access to the
host.
"""
self.slavedir = slavedir
self.baseFilename = baseFilename
self.scpString = scpString
ShellCommand.__init__(self, **kwargs)
def start(self):
# we need to append some additional information to the package name
# to make sure we don't overwrite any existing packages
changes = self.step_status.build.getChanges()
args = parseSendchangeArguments(changes[0].files)
# the REMOTE_USER from the submission form
changer = changes[0].who
# the time the change was processed, this should be the same for every
# build in a set
when = strftime("%Y-%m-%d_%H:%M", localtime(changes[0].when))
dir = "%s-%s-%s" % (when, changer, self.getProperty('identifier'))
# this is the filename of the package built on the slave
filename = "%s-%s" % (self.getProperty('identifier'), self.baseFilename)
# the path to the package + the filename
slavesrc = path.join(self.slavedir, filename)
# the full path + full filename to the package
# the filename is prepended with the submission time and submitter so
# it can be sorted by the server on the other end
self.scpString = path.join(self.scpString, dir, "%s-%s"
% (changer, filename))
self.setCommand(["scp", slavesrc, self.scpString])
ShellCommand.start(self)
class MozillaTryServerHgClone(Mercurial):
haltOnFailure = True
flunkOnFailure = True
def __init__(self, baseURL="http://hg.mozilla.org/",
defaultBranch='mozilla-central', **kwargs):
timeout = 3600
if 'timeout' in kwargs:
timeout = kwargs['timeout']
# repourl overridden in startVC
Mercurial.__init__(self, baseURL=baseURL, defaultBranch=defaultBranch,
timeout=timeout, **kwargs)
def startVC(self, branch, revision, patch):
branch = self.getProperty('branch')
if branch != 'PATCH_TRY' and branch != 'HG_TRY':
self.repourl = urljoin(self.baseURL, branch)
else:
self.repourl = self.getProperty('mozillaRepoPath')
Mercurial.startVC(self, None, revision, patch)
class MozillaClientMk(ShellCommand):
haltOnFailure = True
flunkOnFailure = True
name = "checkout client.mk"
description = ["fetching client.mk"]
descriptionDone = ["client.mk"]
def __init__(self, cvsroot, **kwargs):
self.cvsroot = cvsroot
self.workdir = "."
kwargs['workdir'] = "."
# command may be overridden in start()
kwargs['command'] = ["cvs", "-d", cvsroot, "co", "mozilla/client.mk"]
ShellCommand.__init__(self, **kwargs)
def start(self):
changes = self.step_status.build.getChanges()
args = parseSendchangeArguments(changes[0].files)
self.command = ["cvs", "-d", self.cvsroot, "co"]
if 'branch' in args:
self.command.extend(["-r", args['branch']])
self.command.append("mozilla/client.mk")
ShellCommand.start(self)
class MozillaCustomPatch(ShellCommand):
"""This step looks at a Change to find the name of a diff.
The diff is applied to the current tree.
This step is typically used in conjunction with the MozillaPatchDownload
step.
"""
haltOnFailure = True
def __init__(self, **kwargs):
"""
'workdir' is assumed to be 'build' and should be passed if it is
anything else.
'isOptional' is assumed to be False; if the patch is optional, pass True.
"""
if 'isOptional' in kwargs:
self.optional = kwargs['isOptional']
else:
self.optional = False
if not 'workdir' in kwargs:
kwargs['workdir'] = "build"
ShellCommand.__init__(self, **kwargs)
def start(self):
changes = self.step_status.build.getChanges()
if len(changes) < 1:
log.msg("No changes, not doing anything")
self.step_status.setColor("yellow")
self.step_status.setText(["Skipped patch step:", "no patch"])
self.finished(SKIPPED)
return SKIPPED
if len(changes) > 1:
log.msg("Ignoring all but the first change...")
args = parseSendchangeArguments(changes[0].files)
if not 'patchFile' in args and self.optional:
return SKIPPED
self.setCommand(["patch", "-f", "-p%d" % int(args['patchLevel']), "-i",
args['patchFile']])
ShellCommand.start(self)
class MozillaCreateUploadDirectory(ShellCommand):
def __init__(self, scpString, **kwargs):
(self.sshHost, self.sshDir) = scpString.split(":")
ShellCommand.__init__(self, **kwargs)
def start(self):
changes = self.step_status.build.getChanges()
when = strftime("%Y-%m-%d_%H:%M", localtime(changes[0].when))
changer = changes[0].who
args = parseSendchangeArguments(changes[0].files)
dir = "%s-%s-%s" % (when, changer, self.getProperty('identifier'))
fullDir = path.join(self.sshDir, dir)
self.setCommand(['ssh', self.sshHost, 'mkdir', fullDir])
ShellCommand.start(self)
def evaluateCommand(self, cmd):
result = ShellCommand.evaluateCommand(self, cmd)
if None != re.search('File exists', cmd.logs['stdio'].getText()):
result = SUCCESS
return result