OLD | NEW |
| (Empty) |
1 # -*- python -*- | |
2 # ex: set syntax=python: | |
3 | |
4 # These modules come from scripts/master, which must be in the PYTHONPATH. | |
5 # TODO(maruel): Upstream these to make it webkit.org-friendly. | |
6 from builders_pools import BuildersPools | |
7 from try_job_http import TryJobHTTP | |
8 from try_mail_notifier import TryMailNotifier | |
9 #from try_job_svn import TryJobSubversion | |
10 | |
11 | |
12 c = BuildmasterConfig = {} | |
13 | |
14 from buildbot.buildslave import BuildSlave | |
15 from buildbot.changes.pb import PBChangeSource | |
16 from buildbot.scheduler import AnyBranchScheduler, Triggerable | |
17 from buildbot.status import html | |
18 from buildbot.process import buildstep, factory, properties | |
19 from buildbot.steps import master, shell, source, transfer, trigger | |
20 from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS, SKIPPED | |
21 | |
22 from twisted.internet import defer | |
23 | |
24 import re | |
25 import simplejson | |
26 | |
27 WithProperties = properties.WithProperties | |
28 | |
29 class ConfigureBuild(buildstep.BuildStep): | |
30 name = "configure build" | |
31 description = ["configuring build"] | |
32 descriptionDone = ["configured build"] | |
33 def __init__(self, platform, configuration, architecture, buildOnly, *args,
**kwargs): | |
34 buildstep.BuildStep.__init__(self, *args, **kwargs) | |
35 self.platform = platform.split('-', 1)[0] | |
36 self.fullPlatform = platform | |
37 self.configuration = configuration | |
38 self.architecture = architecture | |
39 self.buildOnly = buildOnly | |
40 self.addFactoryArguments(platform=platform, configuration=configuration,
architecture=architecture, buildOnly=buildOnly) | |
41 | |
42 def start(self): | |
43 self.setProperty("platform", self.platform) | |
44 self.setProperty("fullPlatform", self.fullPlatform) | |
45 self.setProperty("configuration", self.configuration) | |
46 self.setProperty("architecture", self.architecture) | |
47 self.setProperty("buildOnly", self.buildOnly) | |
48 self.finished(SUCCESS) | |
49 return defer.succeed(None) | |
50 | |
51 | |
52 class CheckOutSource(source.SVN): | |
53 baseURL = "http://svn.webkit.org/repository/webkit/" | |
54 mode = "update" | |
55 def __init__(self, *args, **kwargs): | |
56 source.SVN.__init__(self, baseURL=self.baseURL, defaultBranch="trunk", m
ode=self.mode, *args, **kwargs) | |
57 | |
58 | |
59 class InstallWin32Dependencies(shell.Compile): | |
60 description = ["installing dependencies"] | |
61 descriptionDone = ["installed dependencies"] | |
62 command = ["perl", "./WebKitTools/Scripts/update-webkit-auxiliary-libs"] | |
63 | |
64 | |
65 class InstallChromiumDependencies(shell.ShellCommand): | |
66 name = "gclient" | |
67 description = ["updating chromium dependencies"] | |
68 descriptionDone = ["updated chromium dependencies"] | |
69 command = ["perl", "./WebKitTools/Scripts/update-webkit-chromium"] | |
70 haltOnFailure = True | |
71 | |
72 | |
73 def appendCustomBuildFlags(step, platform): | |
74 if platform in ('gtk', 'wx', 'qt', 'chromium'): | |
75 step.setCommand(step.command + ['--' + platform]) | |
76 | |
77 | |
78 class CompileWebKit(shell.Compile): | |
79 command = ["perl", "./WebKitTools/Scripts/build-webkit", WithProperties("--%
(configuration)s")] | |
80 env = {'MFLAGS':''} | |
81 name = "compile-webkit" | |
82 description = ["compiling"] | |
83 descriptionDone = ["compiled"] | |
84 warningPattern = ".*arning: .*" | |
85 | |
86 def start(self): | |
87 platform = self.getProperty('platform') | |
88 buildOnly = self.getProperty('buildOnly') | |
89 if platform == 'mac' and buildOnly: | |
90 self.setCommand(self.command + ['DEBUG_INFORMATION_FORMAT=dwarf-with
-dsym']) | |
91 | |
92 appendCustomBuildFlags(self, platform) | |
93 return shell.Compile.start(self) | |
94 | |
95 | |
96 class ArchiveBuiltProduct(shell.ShellCommand): | |
97 command = ["python", "./WebKitTools/BuildSlaveSupport/built-product-archive"
, | |
98 WithProperties("--platform=%(platform)s"), WithProperties("--%(co
nfiguration)s"), "archive"] | |
99 name = "archive-built-product" | |
100 description = ["archiving built product"] | |
101 descriptionDone = ["archived built product"] | |
102 haltOnFailure = True | |
103 | |
104 | |
105 class ExtractBuiltProduct(shell.ShellCommand): | |
106 command = ["python", "./WebKitTools/BuildSlaveSupport/built-product-archive"
, | |
107 WithProperties("--platform=%(platform)s"), WithProperties("--%(co
nfiguration)s"), "extract"] | |
108 name = "extract-built-product" | |
109 description = ["extracting built product"] | |
110 descriptionDone = ["extracted built product"] | |
111 haltOnFailure = True | |
112 | |
113 | |
114 class UploadBuiltProduct(transfer.FileUpload): | |
115 slavesrc = WithProperties("WebKitBuild/%(configuration)s.zip") | |
116 masterdest = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(co
nfiguration)s/%(got_revision)s.zip") | |
117 haltOnFailure = True | |
118 | |
119 def __init__(self): | |
120 transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest) | |
121 | |
122 | |
123 class DownloadBuiltProduct(transfer.FileDownload): | |
124 slavedest = WithProperties("WebKitBuild/%(configuration)s.zip") | |
125 mastersrc = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(con
figuration)s/%(got_revision)s.zip") | |
126 haltOnFailure = True | |
127 | |
128 def __init__(self): | |
129 transfer.FileDownload.__init__(self, self.mastersrc, self.slavedest) | |
130 | |
131 | |
132 class RunJavaScriptCoreTests(shell.Test): | |
133 name = "jscore-test" | |
134 description = ["jscore-tests running"] | |
135 descriptionDone = ["jscore-tests"] | |
136 command = ["perl", "./WebKitTools/Scripts/run-javascriptcore-tests", WithPro
perties("--%(configuration)s")] | |
137 logfiles = {'results': 'JavaScriptCore/tests/mozilla/actual.html'} | |
138 | |
139 def __init__(self, skipBuild=False, *args, **kwargs): | |
140 self.skipBuild = skipBuild | |
141 shell.Test.__init__(self, *args, **kwargs) | |
142 self.addFactoryArguments(skipBuild=skipBuild) | |
143 | |
144 def start(self): | |
145 appendCustomBuildFlags(self, self.getProperty('platform')) | |
146 if self.skipBuild: | |
147 self.setCommand(self.command + ['--skip-build']) | |
148 return shell.Test.start(self) | |
149 | |
150 def commandComplete(self, cmd): | |
151 shell.Test.commandComplete(self, cmd) | |
152 | |
153 logText = cmd.logs['stdio'].getText() | |
154 statusLines = [line for line in logText.splitlines() if line.find('regre
ssion') >= 0 and line.find(' found.') >= 0] | |
155 if statusLines and statusLines[0].split()[0] != '0': | |
156 self.regressionLine = statusLines[0] | |
157 else: | |
158 self.regressionLine = None | |
159 | |
160 def evaluateCommand(self, cmd): | |
161 if self.regressionLine: | |
162 return FAILURE | |
163 | |
164 if cmd.rc != 0: | |
165 return FAILURE | |
166 | |
167 return SUCCESS | |
168 | |
169 def getText(self, cmd, results): | |
170 return self.getText2(cmd, results) | |
171 | |
172 def getText2(self, cmd, results): | |
173 if results != SUCCESS and self.regressionLine: | |
174 return [self.name, self.regressionLine] | |
175 | |
176 return [self.name] | |
177 | |
178 | |
179 class RunWebKitTests(shell.Test): | |
180 name = "layout-test" | |
181 description = ["layout-tests running"] | |
182 descriptionDone = ["layout-tests"] | |
183 command = ["perl", "./WebKitTools/Scripts/run-webkit-tests", "--no-launch-sa
fari", "--no-new-test-results", | |
184 "--no-sample-on-timeout", "--results-directory", "layout-test-res
ults", "--use-remote-links-to-tests", | |
185 WithProperties("--%(configuration)s"), "--exit-after-n-failures",
"20"] | |
186 | |
187 def __init__(self, skipBuild=False, *args, **kwargs): | |
188 self.skipBuild = skipBuild | |
189 shell.Test.__init__(self, *args, **kwargs) | |
190 self.addFactoryArguments(skipBuild=skipBuild) | |
191 | |
192 def start(self): | |
193 appendCustomBuildFlags(self, self.getProperty('platform')) | |
194 if self.skipBuild: | |
195 self.setCommand(self.command + ['--root=WebKitBuild/bin']) | |
196 return shell.Test.start(self) | |
197 | |
198 def commandComplete(self, cmd): | |
199 shell.Test.commandComplete(self, cmd) | |
200 | |
201 logText = cmd.logs['stdio'].getText() | |
202 incorrectLayoutLines = [] | |
203 for line in logText.splitlines(): | |
204 if line.find('had incorrect layout') >= 0 or line.find('were new') >
= 0 or line.find('was new') >= 0: | |
205 incorrectLayoutLines.append(line) | |
206 elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or
line.find(' timed out') >= 0): | |
207 incorrectLayoutLines.append(line) | |
208 elif line.startswith("WARNING:") and line.find(' leak') >= 0: | |
209 incorrectLayoutLines.append(line.replace('WARNING: ', '')) | |
210 elif line.find('Exiting early') >= 0: | |
211 incorrectLayoutLines.append(line) | |
212 | |
213 # FIXME: Detect and summarize leaks of RefCounted objects | |
214 | |
215 self.incorrectLayoutLines = incorrectLayoutLines | |
216 | |
217 def evaluateCommand(self, cmd): | |
218 if self.incorrectLayoutLines: | |
219 if len(self.incorrectLayoutLines) == 1: | |
220 line = self.incorrectLayoutLines[0] | |
221 if line.find('were new') >= 0 or line.find('was new') >= 0 or li
ne.find(' leak') >= 0: | |
222 return WARNINGS | |
223 | |
224 return FAILURE | |
225 | |
226 if cmd.rc != 0: | |
227 return FAILURE | |
228 | |
229 return SUCCESS | |
230 | |
231 def getText(self, cmd, results): | |
232 return self.getText2(cmd, results) | |
233 | |
234 def getText2(self, cmd, results): | |
235 if results != SUCCESS and self.incorrectLayoutLines: | |
236 return self.incorrectLayoutLines | |
237 | |
238 return [self.name] | |
239 | |
240 | |
241 class RunWebKitLeakTests(RunWebKitTests): | |
242 def start(self): | |
243 self.setCommand(self.command + ["--leaks"]) | |
244 return RunWebKitTests.start(self) | |
245 | |
246 | |
247 class ArchiveTestResults(shell.ShellCommand): | |
248 command = ["python", "./WebKitTools/BuildSlaveSupport/test-result-archive", | |
249 WithProperties("--platform=%(platform)s"), WithProperties("--%(co
nfiguration)s"), "archive"] | |
250 name = "archive-test-results" | |
251 description = ["archiving test results"] | |
252 descriptionDone = ["archived test results"] | |
253 haltOnFailure = True | |
254 | |
255 | |
256 class UploadTestResults(transfer.FileUpload): | |
257 slavesrc = "layout-test-results.zip" | |
258 masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revi
sion)s (%(buildnumber)s).zip") | |
259 | |
260 def __init__(self): | |
261 transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest) | |
262 | |
263 | |
264 class ExtractTestResults(master.MasterShellCommand): | |
265 zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revisio
n)s (%(buildnumber)s).zip") | |
266 resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got
_revision)s (%(buildnumber)s)") | |
267 | |
268 def __init__(self): | |
269 master.MasterShellCommand.__init__(self, "") | |
270 | |
271 def start(self): | |
272 self.command = ["ditto", "-k", "-x", "-V", self.build.getProperties().re
nder(self.zipFile), self.build.getProperties().render(self.resultDirectory)] | |
273 return master.MasterShellCommand.start(self) | |
274 | |
275 def finished(self, result): | |
276 url = self.build.getProperties().render(self.resultDirectory).replace("p
ublic_html/", "") | |
277 self.addURL("view results", url) | |
278 result = master.MasterShellCommand.finished(self, result) | |
279 self.step_status.setText(["uploaded results"]) | |
280 return result | |
281 | |
282 | |
283 class Factory(factory.BuildFactory): | |
284 def __init__(self, platform, configuration, architectures, buildOnly): | |
285 factory.BuildFactory.__init__(self) | |
286 self.addStep(ConfigureBuild, platform=platform, configuration=configurat
ion, architecture=" ".join(architectures), buildOnly=buildOnly) | |
287 self.addStep(CheckOutSource) | |
288 if platform == "win": | |
289 self.addStep(InstallWin32Dependencies) | |
290 if platform == "chromium": | |
291 self.addStep(InstallChromiumDependencies) | |
292 | |
293 class BuildFactory(Factory): | |
294 def __init__(self, platform, configuration, architectures, triggers=None): | |
295 Factory.__init__(self, platform, configuration, architectures, True) | |
296 self.addStep(CompileWebKit) | |
297 if triggers: | |
298 self.addStep(ArchiveBuiltProduct) | |
299 self.addStep(UploadBuiltProduct) | |
300 self.addStep(trigger.Trigger, schedulerNames=triggers) | |
301 | |
302 class TestFactory(Factory): | |
303 def __init__(self, platform, configuration, architectures): | |
304 Factory.__init__(self, platform, configuration, architectures, False) | |
305 self.addStep(DownloadBuiltProduct) | |
306 self.addStep(ExtractBuiltProduct) | |
307 self.addStep(RunJavaScriptCoreTests, skipBuild=True) | |
308 self.addStep(RunWebKitTests, skipBuild=(platform == 'win')) | |
309 self.addStep(ArchiveTestResults) | |
310 self.addStep(UploadTestResults) | |
311 self.addStep(ExtractTestResults) | |
312 | |
313 class BuildAndTestFactory(Factory): | |
314 TestClass = RunWebKitTests | |
315 def __init__(self, platform, configuration, architectures): | |
316 Factory.__init__(self, platform, configuration, architectures, False) | |
317 self.addStep(CompileWebKit) | |
318 self.addStep(RunJavaScriptCoreTests) | |
319 self.addStep(self.TestClass) | |
320 self.addStep(ArchiveTestResults) | |
321 self.addStep(UploadTestResults) | |
322 self.addStep(ExtractTestResults) | |
323 | |
324 class BuildAndTestLeaksFactory(BuildAndTestFactory): | |
325 TestClass = RunWebKitLeakTests | |
326 | |
327 | |
328 class TryFactory(Factory): | |
329 """Similar to BuildAndTestFactory but doesn't archive the results.""" | |
330 TestClass = RunWebKitTests | |
331 def __init__(self, platform, configuration, architectures): | |
332 Factory.__init__(self, platform, configuration, architectures, False) | |
333 self.addStep(CompileWebKit) | |
334 self.addStep(RunJavaScriptCoreTests) | |
335 self.addStep(self.TestClass) | |
336 | |
337 | |
338 class TryBuildFactory(BuildFactory): | |
339 """Same as BuildFactory.""" | |
340 pass | |
341 | |
342 | |
343 class TryLeaksFactory(TryFactory): | |
344 TestClass = RunWebKitLeakTests | |
345 | |
346 | |
347 def convertToString(json_data): | |
348 """Converts unicode strings to ascii strings in dictionaries and lists. | |
349 | |
350 Some simplejson version will always return unicode strings. Python | |
351 doesn't accept unicode strings as dictionary key for kwargs so convert | |
352 them.""" | |
353 if isinstance(json_data, dict): | |
354 retval = {} | |
355 for (k,v) in json_data.iteritems(): | |
356 retval[str(k)] = convertToString(v) | |
357 return retval | |
358 elif isinstance(json_data, list): | |
359 retval = [] | |
360 for i in json_data: | |
361 retval.append(convertToString(i)) | |
362 return retval | |
363 elif isinstance(json_data, unicode): | |
364 return str(json_data) | |
365 else: | |
366 return json_data | |
367 | |
368 | |
369 def loadBuilderConfig(c, config_file, passwords_file): | |
370 passwords = convertToString(simplejson.load(open(passwords_file))) | |
371 | |
372 config = convertToString(simplejson.load(open(config_file))) | |
373 | |
374 c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_build
s=1) for slave in config['slaves']] | |
375 | |
376 c['schedulers'] = [] | |
377 for scheduler in config['schedulers']: | |
378 kls = globals()[scheduler.pop('type')] | |
379 c['schedulers'].append(kls(**scheduler)) | |
380 | |
381 c['builders'] = [] | |
382 for builder in config['builders']: | |
383 for slaveName in builder['slavenames']: | |
384 for slave in config['slaves']: | |
385 if slave['name'] != slaveName or slave['platform'] == '*': | |
386 continue | |
387 | |
388 if slave['platform'] != builder['platform']: | |
389 raise Exception, "Builder %r is for platform %r but has slav
e %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], s
lave['platform']) | |
390 | |
391 break | |
392 | |
393 factory = globals()["%sFactory" % builder.pop('type')] | |
394 factoryArgs = [] | |
395 for key in "platform", "configuration", "architectures", "triggers": | |
396 value = builder.pop(key, None) | |
397 if value: | |
398 factoryArgs.append(value) | |
399 | |
400 builder["factory"] = factory(*factoryArgs) | |
401 | |
402 c['builders'].append(builder) | |
403 | |
404 | |
405 loadBuilderConfig(c, 'config.json', 'passwords.json') | |
406 | |
407 c['change_source'] = PBChangeSource() | |
408 pools = BuildersPools('webkit') | |
409 #pools['webkit'].append("Try Apple Leopard Intel Release") | |
410 pools['webkit'].append("Try Apple Leopard Intel Debug") | |
411 #pools['webkit'].append("Try Apple SnowLeopard Intel Release") | |
412 #pools['webkit'].append("Try Apple SnowLeopard Intel Leaks") | |
413 #pools['webkit'].append("Try Apple Windows Release") | |
414 pools['webkit'].append("Try Apple Windows Debug") | |
415 #pools['webkit'].append("Try GTK Linux Release") | |
416 #pools['webkit'].append("Try Qt Linux Release") | |
417 pools['webkit'].append("Try Chromium Win Release") | |
418 pools['webkit'].append("Try Chromium Mac Release") | |
419 pools['webkit'].append("Try Chromium Linux Release") | |
420 c['schedulers'].append(TryJobHTTP( | |
421 name='try_job_http', | |
422 port=8017, | |
423 pools=pools)) | |
424 | |
425 c['status'] = [] | |
426 c['status'].append(html.WebStatus(http_port=8010, allowForce=True)) | |
427 c['status'].append(html.WebStatus(http_port=8044, allowForce=False)) | |
428 | |
429 def GetSmtp(): | |
430 # TODO(maruel): Remove me. | |
431 import chromium_config_private | |
432 return chromium_config_private.Master.smtp | |
433 | |
434 c['status'].append(TryMailNotifier( | |
435 fromaddr='tryserver@chromium.org', | |
436 subject="try %(result)s for %(reason)s on %(builder)s @ r%(revision)s", | |
437 mode='all', | |
438 relayhost=GetSmtp(), | |
439 lookup='')) | |
440 | |
441 | |
442 c['slavePortnum'] = 8031 | |
443 c['projectName'] = "WebKit" | |
444 c['projectURL'] = "http://webkit.org" | |
445 c['buildbotURL'] = "http://build.chromium.org/buildbot/webkit-try/" | |
446 | |
447 # Keep last try jobs, the default is too low. Must keep at least a few days | |
448 # worth of try jobs. | |
449 c['buildHorizon'] = 3000 | |
450 c['logHorizon'] = 3000 | |
451 # Must be at least 2x the number of slaves. | |
452 c['eventHorizon'] = 200 | |
453 # Must be at least 2x the number of on-going builds. | |
454 c['buildCacheSize'] = 200 | |
455 | |
OLD | NEW |