Package buildbot :: Package process :: Module buildrequest
[frames] | no frames]

Source Code for Module buildbot.process.buildrequest

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  import calendar 
 17  from zope.interface import implements 
 18  from twisted.python import log 
 19  from twisted.internet import defer 
 20  from buildbot import interfaces, sourcestamp 
 21  from buildbot.process import properties 
 22  from buildbot.status.results import FAILURE 
 23  from buildbot.db import buildrequests 
24 25 -class BuildRequest(object):
26 """ 27 28 A rolled-up encapsulation of all of the data relevant to a build request. 29 30 This class is used by the C{nextBuild} and C{mergeRequests} configuration 31 parameters, as well as in starting a build. Construction of a BuildRequest 32 object is a heavyweight process involving a lot of database queries, so 33 it should be avoided where possible. See bug #1894. 34 35 Build requests have a SourceStamp which specifies what sources to build. 36 This may specify a specific revision of the source tree (so source.branch, 37 source.revision, and source.patch are used). The .patch attribute is either 38 None or a tuple of (patchlevel, diff), consisting of a number to use in 39 'patch -pN', and a unified-format context diff. 40 41 Alternatively, the SourceStamp may specify a set of Changes to be built, 42 contained in source.changes. In this case, the requeset may be mergeable 43 with other BuildRequests on the same branch. 44 45 @type source: L{buildbot.sourcestamp.SourceStamp} 46 @ivar source: the source stamp that this BuildRequest use 47 48 @type reason: string 49 @ivar reason: the reason this Build is being requested. Schedulers provide 50 this, but for forced builds the user requesting the build will provide a 51 string. It comes from the buildsets table. 52 53 @type properties: L{properties.Properties} 54 @ivar properties: properties that should be applied to this build, taken 55 from the buildset containing this build request 56 57 @ivar submittedAt: a timestamp (seconds since epoch) when this request was 58 submitted to the Builder. This is used by the CVS step to compute a 59 checkout timestamp, as well as by the master to prioritize build requests 60 from oldest to newest. 61 62 @ivar buildername: name of the requested builder 63 64 @ivar priority: request priority 65 66 @ivar id: build request ID 67 68 @ivar bsid: ID of the parent buildset 69 """ 70 71 source = None 72 sources = None 73 submittedAt = None 74 75 @classmethod
76 - def fromBrdict(cls, master, brdict):
77 """ 78 Construct a new L{BuildRequest} from a dictionary as returned by 79 L{BuildRequestsConnectorComponent.getBuildRequest}. 80 81 This method uses a cache, which may result in return of stale objects; 82 for the most up-to-date information, use the database connector 83 methods. 84 85 @param master: current build master 86 @param brdict: build request dictionary 87 88 @returns: L{BuildRequest}, via Deferred 89 """ 90 cache = master.caches.get_cache("BuildRequests", cls._make_br) 91 return cache.get(brdict['brid'], brdict=brdict, master=master)
92 93 @classmethod 94 @defer.inlineCallbacks
95 - def _make_br(cls, brid, brdict, master):
96 buildrequest = cls() 97 buildrequest.id = brid 98 buildrequest.bsid = brdict['buildsetid'] 99 buildrequest.buildername = brdict['buildername'] 100 buildrequest.priority = brdict['priority'] 101 dt = brdict['submitted_at'] 102 buildrequest.submittedAt = dt and calendar.timegm(dt.utctimetuple()) 103 buildrequest.master = master 104 105 # fetch the buildset to get the reason 106 buildset = yield master.db.buildsets.getBuildset(brdict['buildsetid']) 107 assert buildset # schema should guarantee this 108 buildrequest.reason = buildset['reason'] 109 110 # fetch the buildset properties, and convert to Properties 111 buildset_properties = yield master.db.buildsets.getBuildsetProperties(brdict['buildsetid']) 112 113 buildrequest.properties = properties.Properties.fromDict(buildset_properties) 114 115 # fetch the sourcestamp dictionary 116 sslist = yield master.db.sourcestamps.getSourceStamps(buildset['sourcestampsetid']) 117 assert len(sslist) > 0, "Empty sourcestampset: db schema enforces set to exist but cannot enforce a non empty set" 118 119 # and turn it into a SourceStamps 120 buildrequest.sources = {} 121 def store_source(source): 122 buildrequest.sources[source.codebase] = source
123 124 dlist = [] 125 for ssdict in sslist: 126 d = sourcestamp.SourceStamp.fromSsdict(master, ssdict) 127 d.addCallback(store_source) 128 dlist.append(d) 129 130 yield defer.gatherResults(dlist) 131 132 if buildrequest.sources: 133 buildrequest.source = buildrequest.sources.values()[0] 134 135 defer.returnValue(buildrequest)
136
137 - def requestsHaveSameCodebases(self, other):
138 self_codebases = set(self.sources.iterkeys()) 139 other_codebases = set(other.sources.iterkeys()) 140 return self_codebases == other_codebases
141
142 - def requestsHaveChangesForSameCodebases(self, other):
143 # Merge can only be done if both requests have sourcestampsets containing 144 # comparable sourcestamps, that means sourcestamps with the same codebase. 145 # This means that both requests must have exact the same set of codebases 146 # If not then merge cannot be performed. 147 # The second requirement is that both request have the changes in the 148 # same codebases. 149 # 150 # Normaly a scheduler always delivers the same set of codebases: 151 # sourcestamps with and without changes 152 # For the case a scheduler is not configured with a set of codebases 153 # it delivers only a set with sourcestamps that have changes. 154 self_codebases = set(self.sources.iterkeys()) 155 other_codebases = set(other.sources.iterkeys()) 156 if self_codebases != other_codebases: 157 return False 158 159 for c in self_codebases: 160 # Check either both or neither have changes 161 if ((len(self.sources[c].changes) > 0) 162 != (len(other.sources[c].changes) > 0)): 163 return False 164 # all codebases tested, no differences found 165 return True
166
167 - def canBeMergedWith(self, other):
168 """ 169 Returns if both requests can be merged 170 """ 171 172 if not self.requestsHaveChangesForSameCodebases(other): 173 return False 174 175 #get codebases from myself, they are equal to other 176 self_codebases = set(self.sources.iterkeys()) 177 178 for c in self_codebases: 179 # check to prevent exception 180 if c not in other.sources: 181 return False 182 if not self.sources[c].canBeMergedWith(other.sources[c]): 183 return False 184 return True
185
186 - def mergeSourceStampsWith(self, others):
187 """ Returns one merged sourcestamp for every codebase """ 188 #get all codebases from all requests 189 all_codebases = set(self.sources.iterkeys()) 190 for other in others: 191 all_codebases |= set(other.sources.iterkeys()) 192 193 all_merged_sources = {} 194 # walk along the codebases 195 for codebase in all_codebases: 196 all_sources = [] 197 if codebase in self.sources: 198 all_sources.append(self.sources[codebase]) 199 for other in others: 200 if codebase in other.sources: 201 all_sources.append(other.sources[codebase]) 202 assert len(all_sources)>0, "each codebase should have atleast one sourcestamp" 203 all_merged_sources[codebase] = all_sources[0].mergeWith(all_sources[1:]) 204 205 return [source for source in all_merged_sources.itervalues()]
206
207 - def mergeReasons(self, others):
208 """Return a reason for the merged build request.""" 209 reasons = [] 210 for req in [self] + others: 211 if req.reason and req.reason not in reasons: 212 reasons.append(req.reason) 213 return ", ".join(reasons)
214
215 - def getSubmitTime(self):
216 return self.submittedAt
217 218 @defer.inlineCallbacks
219 - def cancelBuildRequest(self):
220 # first, try to claim the request; if this fails, then it's too late to 221 # cancel the build anyway 222 try: 223 yield self.master.db.buildrequests.claimBuildRequests([self.id]) 224 except buildrequests.AlreadyClaimedError: 225 log.msg("build request already claimed; cannot cancel") 226 return 227 228 # then complete it with 'FAILURE'; this is the closest we can get to 229 # cancelling a request without running into trouble with dangling 230 # references. 231 yield self.master.db.buildrequests.completeBuildRequests([self.id], 232 FAILURE) 233 234 # and let the master know that the enclosing buildset may be complete 235 yield self.master.maybeBuildsetComplete(self.bsid)
236
237 -class BuildRequestControl:
238 implements(interfaces.IBuildRequestControl) 239
240 - def __init__(self, builder, request):
241 self.original_builder = builder 242 self.original_request = request 243 self.brid = request.id
244
245 - def subscribe(self, observer):
246 raise NotImplementedError
247
248 - def unsubscribe(self, observer):
249 raise NotImplementedError
250
251 - def cancel(self):
252 d = self.original_request.cancelBuildRequest() 253 d.addErrback(log.err, 'while cancelling build request')
254