phase2: move phase2 specific options into separate section
[buildbot.git] / phase2 / master.cfg
1 # -*- python -*-
2 # ex: set syntax=python:
3
4 import os
5 import re
6 import base64
7 import subprocess
8 import ConfigParser
9
10 from buildbot import locks
11
12 ini = ConfigParser.ConfigParser()
13 ini.read(os.getenv("BUILDMASTER_CONFIG", "./config.ini"))
14
15 buildbot_url = ini.get("phase2", "buildbot_url")
16
17 # This is a sample buildmaster config file. It must be installed as
18 # 'master.cfg' in your buildmaster's base directory.
19
20 # This is the dictionary that the buildmaster pays attention to. We also use
21 # a shorter alias to save typing.
22 c = BuildmasterConfig = {}
23
24 ####### BUILDSLAVES
25
26 # The 'slaves' list defines the set of recognized buildslaves. Each element is
27 # a BuildSlave object, specifying a unique slave name and password. The same
28 # slave name and password must be configured on the slave.
29 from buildbot.buildslave import BuildSlave
30
31 slave_port = 9990
32 persistent = False
33 other_builds = 0
34 tree_expire = 0
35 git_ssh = False
36 git_ssh_key = None
37
38 if ini.has_option("phase2", "port"):
39 slave_port = ini.getint("phase2", "port")
40
41 if ini.has_option("phase2", "persistent"):
42 persistent = ini.getboolean("phase2", "persistent")
43
44 if ini.has_option("phase2", "other_builds"):
45 other_builds = ini.getint("phase2", "other_builds")
46
47 if ini.has_option("phase2", "expire"):
48 tree_expire = ini.getint("phase2", "expire")
49
50 if ini.has_option("general", "git_ssh"):
51 git_ssh = ini.getboolean("general", "git_ssh")
52
53 if ini.has_option("general", "git_ssh_key"):
54 git_ssh_key = ini.get("general", "git_ssh_key")
55 else:
56 git_ssh = False
57
58 c['slaves'] = []
59 max_builds = dict()
60
61 for section in ini.sections():
62 if section.startswith("slave "):
63 if ini.has_option(section, "name") and ini.has_option(section, "password") and \
64 ini.has_option(section, "phase") and ini.getint(section, "phase") == 2:
65 name = ini.get(section, "name")
66 password = ini.get(section, "password")
67 max_builds[name] = 1
68 if ini.has_option(section, "builds"):
69 max_builds[name] = ini.getint(section, "builds")
70 c['slaves'].append(BuildSlave(name, password, max_builds = max_builds[name]))
71
72 # 'slavePortnum' defines the TCP port to listen on for connections from slaves.
73 # This must match the value configured into the buildslaves (with their
74 # --master option)
75 c['slavePortnum'] = slave_port
76
77 # coalesce builds
78 c['mergeRequests'] = True
79
80 # Reduce amount of backlog data
81 c['buildHorizon'] = 30
82 c['logHorizon'] = 20
83
84 ####### CHANGESOURCES
85
86 work_dir = os.path.abspath(ini.get("general", "workdir") or ".")
87 scripts_dir = os.path.abspath("../scripts")
88
89 rsync_bin_url = ini.get("rsync", "binary_url")
90 rsync_bin_key = ini.get("rsync", "binary_password")
91
92 rsync_src_url = None
93 rsync_src_key = None
94
95 if ini.has_option("rsync", "source_url"):
96 rsync_src_url = ini.get("rsync", "source_url")
97 rsync_src_key = ini.get("rsync", "source_password")
98
99 rsync_sdk_url = None
100 rsync_sdk_key = None
101 rsync_sdk_pat = "openwrt-sdk-*.tar.xz"
102
103 if ini.has_option("rsync", "sdk_url"):
104 rsync_sdk_url = ini.get("rsync", "sdk_url")
105
106 if ini.has_option("rsync", "sdk_password"):
107 rsync_sdk_key = ini.get("rsync", "sdk_password")
108
109 if ini.has_option("rsync", "sdk_pattern"):
110 rsync_sdk_pat = ini.get("rsync", "sdk_pattern")
111
112 repo_url = ini.get("repo", "url")
113 repo_branch = "master"
114
115 if ini.has_option("repo", "branch"):
116 repo_branch = ini.get("repo", "branch")
117
118 usign_key = None
119 usign_comment = "untrusted comment: " + repo_branch.replace("-", " ").title() + " key"
120
121 if ini.has_option("usign", "key"):
122 usign_key = ini.get("usign", "key")
123
124 if ini.has_option("usign", "comment"):
125 usign_comment = ini.get("usign", "comment")
126
127
128 # find arches
129 arches = [ ]
130 archnames = [ ]
131
132 if not os.path.isdir(work_dir+'/source.git'):
133 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, work_dir+'/source.git'])
134 else:
135 subprocess.call(["git", "pull"], cwd = work_dir+'/source.git')
136
137 findarches = subprocess.Popen([scripts_dir + '/dumpinfo.pl', 'architectures'],
138 stdout = subprocess.PIPE, cwd = work_dir+'/source.git')
139
140 while True:
141 line = findarches.stdout.readline()
142 if not line:
143 break
144 at = line.strip().split()
145 arches.append(at)
146 archnames.append(at[0])
147
148
149 # find feeds
150 feeds = []
151 feedbranches = dict()
152
153 from buildbot.changes.gitpoller import GitPoller
154 c['change_source'] = []
155
156 def parse_feed_entry(line):
157 parts = line.strip().split()
158 if parts[0] == "src-git":
159 feeds.append(parts)
160 url = parts[2].strip().split(';')
161 branch = url[1] if len(url) > 1 else 'master'
162 feedbranches[url[0]] = branch
163 c['change_source'].append(GitPoller(url[0], branch=branch, workdir='%s/%s.git' %(os.getcwd(), parts[1]), pollinterval=300))
164
165 make = subprocess.Popen(['make', '--no-print-directory', '-C', work_dir+'/source.git/target/sdk/', 'val.BASE_FEED'],
166 env = dict(os.environ, TOPDIR=work_dir+'/source.git'), stdout = subprocess.PIPE)
167
168 line = make.stdout.readline()
169 if line:
170 parse_feed_entry(line)
171
172 with open(work_dir+'/source.git/feeds.conf.default', 'r') as f:
173 for line in f:
174 parse_feed_entry(line)
175
176
177 ####### SCHEDULERS
178
179 # Configure the Schedulers, which decide how to react to incoming changes. In this
180 # case, just kick off a 'basebuild' build
181
182 def branch_change_filter(change):
183 return change.branch == feedbranches[change.repository]
184
185 from buildbot.schedulers.basic import SingleBranchScheduler
186 from buildbot.schedulers.forcesched import ForceScheduler
187 from buildbot.changes import filter
188 c['schedulers'] = []
189 c['schedulers'].append(SingleBranchScheduler(
190 name="all",
191 change_filter=filter.ChangeFilter(filter_fn=branch_change_filter),
192 treeStableTimer=60,
193 builderNames=archnames))
194
195 c['schedulers'].append(ForceScheduler(
196 name="force",
197 builderNames=archnames))
198
199 ####### BUILDERS
200
201 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
202 # what steps, and which slaves can execute them. Note that any particular build will
203 # only take place on one slave.
204
205 from buildbot.process.factory import BuildFactory
206 from buildbot.steps.source import Git
207 from buildbot.steps.shell import ShellCommand
208 from buildbot.steps.shell import SetProperty
209 from buildbot.steps.transfer import FileUpload
210 from buildbot.steps.transfer import FileDownload
211 from buildbot.steps.transfer import StringDownload
212 from buildbot.steps.master import MasterShellCommand
213 from buildbot.process.properties import WithProperties
214
215
216 def GetDirectorySuffix(props):
217 verpat = re.compile('^([0-9]{2})\.([0-9]{2})(?:\.([0-9]+)(?:-rc([0-9]+))?|-(SNAPSHOT))$')
218 if props.hasProperty("release_version"):
219 m = verpat.match(props["release_version"])
220 if m is not None:
221 return "-%02d.%02d" %(int(m.group(1)), int(m.group(2)))
222 return ""
223
224 def GetNumJobs(props):
225 if props.hasProperty("slavename") and props.hasProperty("nproc"):
226 return ((int(props["nproc"]) / (max_builds[props["slavename"]] + other_builds)) + 1)
227 else:
228 return 1
229
230 def GetCwd(props):
231 if props.hasProperty("builddir"):
232 return props["builddir"]
233 elif props.hasProperty("workdir"):
234 return props["workdir"]
235 else:
236 return "/"
237
238 def UsignSec2Pub(seckey, comment="untrusted comment: secret key"):
239 try:
240 seckey = base64.b64decode(seckey)
241 except:
242 return None
243
244 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
245 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
246
247
248 c['builders'] = []
249
250 dlLock = locks.SlaveLock("slave_dl")
251
252 slaveNames = [ ]
253
254 for slave in c['slaves']:
255 slaveNames.append(slave.slavename)
256
257 for arch in arches:
258 ts = arch[1].split('/')
259
260 factory = BuildFactory()
261
262 # find number of cores
263 factory.addStep(SetProperty(
264 name = "nproc",
265 property = "nproc",
266 description = "Finding number of CPUs",
267 command = ["nproc"]))
268
269 # prepare workspace
270 factory.addStep(FileDownload(
271 mastersrc = scripts_dir + '/cleanup-phase2.sh',
272 slavedest = "cleanup.sh",
273 mode = 0755))
274
275 if not persistent:
276 factory.addStep(ShellCommand(
277 name = "cleanold",
278 description = "Cleaning previous builds",
279 command = ["./cleanup.sh", buildbot_url, WithProperties("%(slavename)s"), WithProperties("%(buildername)s"), "full"],
280 haltOnFailure = True,
281 timeout = 2400))
282
283 factory.addStep(ShellCommand(
284 name = "cleanup",
285 description = "Cleaning work area",
286 command = ["./cleanup.sh", buildbot_url, WithProperties("%(slavename)s"), WithProperties("%(buildername)s"), "single"],
287 haltOnFailure = True,
288 timeout = 2400))
289
290 # expire tree if needed
291 elif tree_expire > 0:
292 factory.addStep(FileDownload(
293 mastersrc = scripts_dir + '/expire.sh',
294 slavedest = "../expire.sh",
295 mode = 0755))
296
297 factory.addStep(ShellCommand(
298 name = "expire",
299 description = "Checking for build tree expiry",
300 command = ["./expire.sh", str(tree_expire)],
301 workdir = ".",
302 haltOnFailure = True,
303 timeout = 2400))
304
305 factory.addStep(ShellCommand(
306 name = "mksdkdir",
307 description = "Preparing SDK directory",
308 command = ["mkdir", "-p", "sdk"],
309 haltOnFailure = True))
310
311 factory.addStep(ShellCommand(
312 name = "downloadsdk",
313 description = "Downloading SDK archive",
314 command = ["rsync", "-4", "-va", "%s/%s/%s/%s" %(rsync_sdk_url, ts[0], ts[1], rsync_sdk_pat), "sdk.archive"],
315 env={'RSYNC_PASSWORD': rsync_sdk_key},
316 haltOnFailure = True,
317 logEnviron = False))
318
319 factory.addStep(ShellCommand(
320 name = "unpacksdk",
321 description = "Unpacking SDK archive",
322 command = "rm -rf sdk_update && mkdir sdk_update && tar --strip-components=1 -C sdk_update/ -vxf sdk.archive",
323 haltOnFailure = True))
324
325 factory.addStep(ShellCommand(
326 name = "updatesdk",
327 description = "Updating SDK",
328 command = "rsync --checksum -av sdk_update/ sdk/ && rm -rf sdk_update",
329 haltOnFailure = True))
330
331 factory.addStep(StringDownload(
332 name = "writeversionmk",
333 s = 'TOPDIR:=${CURDIR}\n\ninclude $(TOPDIR)/include/version.mk\n\nversion:\n\t@echo $(VERSION_NUMBER)\n',
334 slavedest = "sdk/getversion.mk",
335 mode = 0755))
336
337 factory.addStep(SetProperty(
338 name = "getversion",
339 property = "release_version",
340 description = "Finding SDK release version",
341 workdir = "build/sdk",
342 command = ["make", "-f", "getversion.mk"]))
343
344 # install build key
345 if usign_key is not None:
346 factory.addStep(StringDownload(
347 name = "dlkeybuildpub",
348 s = UsignSec2Pub(usign_key, usign_comment),
349 slavedest = "sdk/key-build.pub",
350 mode = 0600))
351
352 factory.addStep(StringDownload(
353 name = "dlkeybuild",
354 s = "# fake private key",
355 slavedest = "sdk/key-build",
356 mode = 0600))
357
358 factory.addStep(StringDownload(
359 name = "dlkeybuilducert",
360 s = "# fake certificate",
361 slavedest = "sdk/key-build.ucert",
362 mode = 0600))
363
364 factory.addStep(ShellCommand(
365 name = "mkdldir",
366 description = "Preparing download directory",
367 command = ["sh", "-c", "mkdir -p $HOME/dl && rm -rf ./sdk/dl && ln -sf $HOME/dl ./sdk/dl"],
368 haltOnFailure = True))
369
370 factory.addStep(ShellCommand(
371 name = "mkconf",
372 description = "Preparing SDK configuration",
373 workdir = "build/sdk",
374 command = ["sh", "-c", "rm -f .config && make defconfig"]))
375
376 factory.addStep(FileDownload(
377 mastersrc = scripts_dir + '/ccache.sh',
378 slavedest = 'sdk/ccache.sh',
379 mode = 0755))
380
381 factory.addStep(ShellCommand(
382 name = "prepccache",
383 description = "Preparing ccache",
384 workdir = "build/sdk",
385 command = ["./ccache.sh"],
386 haltOnFailure = True))
387
388 if git_ssh:
389 factory.addStep(StringDownload(
390 name = "dlgitclonekey",
391 s = git_ssh_key,
392 slavedest = "../git-clone.key",
393 mode = 0600))
394
395 factory.addStep(ShellCommand(
396 name = "patchfeedsconf",
397 description = "Patching feeds.conf",
398 workdir = "build/sdk",
399 command = "sed -e 's#https://#ssh://git@#g' feeds.conf.default > feeds.conf",
400 haltOnFailure = True))
401
402 factory.addStep(ShellCommand(
403 name = "updatefeeds",
404 description = "Updating feeds",
405 workdir = "build/sdk",
406 command = ["./scripts/feeds", "update", "-f"],
407 env = {'GIT_SSH_COMMAND': WithProperties("ssh -o IdentitiesOnly=yes -o IdentityFile=%(cwd)s/git-clone.key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", cwd=GetCwd)} if git_ssh else {},
408 haltOnFailure = True))
409
410 if git_ssh:
411 factory.addStep(ShellCommand(
412 name = "rmfeedsconf",
413 description = "Removing feeds.conf",
414 workdir = "build/sdk",
415 command=["rm", "feeds.conf"],
416 haltOnFailure = True))
417
418 factory.addStep(ShellCommand(
419 name = "installfeeds",
420 description = "Installing feeds",
421 workdir = "build/sdk",
422 command = ["./scripts/feeds", "install", "-a"],
423 haltOnFailure = True))
424
425 factory.addStep(ShellCommand(
426 name = "logclear",
427 description = "Clearing failure logs",
428 workdir = "build/sdk",
429 command = ["rm", "-rf", "logs/package/error.txt", "faillogs/"],
430 haltOnFailure = False
431 ))
432
433 factory.addStep(ShellCommand(
434 name = "compile",
435 description = "Building packages",
436 workdir = "build/sdk",
437 timeout = 3600,
438 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "IGNORE_ERRORS=n m y", "BUILD_LOG=1", "CONFIG_AUTOREMOVE=y"],
439 env = {'CCACHE_BASEDIR': WithProperties("%(cwd)s", cwd=GetCwd)},
440 haltOnFailure = True))
441
442 factory.addStep(ShellCommand(
443 name = "mkfeedsconf",
444 description = "Generating pinned feeds.conf",
445 workdir = "build/sdk",
446 command = "./scripts/feeds list -s -f > bin/packages/%s/feeds.conf" %(arch[0])))
447
448 if ini.has_option("gpg", "key") or usign_key is not None:
449 factory.addStep(MasterShellCommand(
450 name = "signprepare",
451 description = "Preparing temporary signing directory",
452 command = ["mkdir", "-p", "%s/signing" %(work_dir)],
453 haltOnFailure = True
454 ))
455
456 factory.addStep(ShellCommand(
457 name = "signpack",
458 description = "Packing files to sign",
459 workdir = "build/sdk",
460 command = "find bin/packages/%s/ -mindepth 2 -maxdepth 2 -type f -name Packages -print0 | xargs -0 tar -czf sign.tar.gz" %(arch[0]),
461 haltOnFailure = True
462 ))
463
464 factory.addStep(FileUpload(
465 slavesrc = "sdk/sign.tar.gz",
466 masterdest = "%s/signing/%s.tar.gz" %(work_dir, arch[0]),
467 haltOnFailure = True
468 ))
469
470 factory.addStep(MasterShellCommand(
471 name = "signfiles",
472 description = "Signing files",
473 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.tar.gz" %(work_dir, arch[0])],
474 env = { 'CONFIG_INI': os.getenv("BUILDMASTER_CONFIG", "./config.ini") },
475 haltOnFailure = True
476 ))
477
478 factory.addStep(FileDownload(
479 mastersrc = "%s/signing/%s.tar.gz" %(work_dir, arch[0]),
480 slavedest = "sdk/sign.tar.gz",
481 haltOnFailure = True
482 ))
483
484 factory.addStep(ShellCommand(
485 name = "signunpack",
486 description = "Unpacking signed files",
487 workdir = "build/sdk",
488 command = ["tar", "-xzf", "sign.tar.gz"],
489 haltOnFailure = True
490 ))
491
492 factory.addStep(ShellCommand(
493 name = "uploadprepare",
494 description = "Preparing package directory",
495 workdir = "build/sdk",
496 command = ["rsync", "-4", "-av", "--include", "/%s/" %(arch[0]), "--exclude", "/*", "--exclude", "/%s/*" %(arch[0]), "bin/packages/", WithProperties("%s/packages%%(suffix)s/" %(rsync_bin_url), suffix=GetDirectorySuffix)],
497 env={'RSYNC_PASSWORD': rsync_bin_key},
498 haltOnFailure = True,
499 logEnviron = False
500 ))
501
502 factory.addStep(ShellCommand(
503 name = "packageupload",
504 description = "Uploading package files",
505 workdir = "build/sdk",
506 command = ["rsync", "-4", "--progress", "--delete", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s" %(arch[0]), "-avz", "bin/packages/%s/" %(arch[0]), WithProperties("%s/packages%%(suffix)s/%s/" %(rsync_bin_url, arch[0]), suffix=GetDirectorySuffix)],
507 env={'RSYNC_PASSWORD': rsync_bin_key},
508 haltOnFailure = True,
509 logEnviron = False
510 ))
511
512 factory.addStep(ShellCommand(
513 name = "logprepare",
514 description = "Preparing log directory",
515 workdir = "build/sdk",
516 command = ["rsync", "-4", "-av", "--include", "/%s/" %(arch[0]), "--exclude", "/*", "--exclude", "/%s/*" %(arch[0]), "bin/packages/", WithProperties("%s/faillogs%%(suffix)s/" %(rsync_bin_url), suffix=GetDirectorySuffix)],
517 env={'RSYNC_PASSWORD': rsync_bin_key},
518 haltOnFailure = True,
519 logEnviron = False
520 ))
521
522 factory.addStep(ShellCommand(
523 name = "logfind",
524 description = "Finding failure logs",
525 workdir = "build/sdk/logs/package/feeds",
526 command = ["sh", "-c", "sed -ne 's!^ *ERROR: package/feeds/\\([^ ]*\\) .*$!\\1!p' ../error.txt | sort -u | xargs -r find > ../../../logs.txt"],
527 haltOnFailure = False
528 ))
529
530 factory.addStep(ShellCommand(
531 name = "logcollect",
532 description = "Collecting failure logs",
533 workdir = "build/sdk",
534 command = ["rsync", "-av", "--files-from=logs.txt", "logs/package/feeds/", "faillogs/"],
535 haltOnFailure = False
536 ))
537
538 factory.addStep(ShellCommand(
539 name = "logupload",
540 description = "Uploading failure logs",
541 workdir = "build/sdk",
542 command = ["rsync", "-4", "--progress", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s" %(arch[0]), "-avz", "faillogs/", WithProperties("%s/faillogs%%(suffix)s/%s/" %(rsync_bin_url, arch[0]), suffix=GetDirectorySuffix)],
543 env={'RSYNC_PASSWORD': rsync_bin_key},
544 haltOnFailure = False,
545 logEnviron = False
546 ))
547
548 if rsync_src_url is not None:
549 factory.addStep(ShellCommand(
550 name = "sourcelist",
551 description = "Finding source archives to upload",
552 workdir = "build/sdk",
553 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -newer ../sdk.archive -printf '%f\\n' > sourcelist",
554 haltOnFailure = True
555 ))
556
557 factory.addStep(ShellCommand(
558 name = "sourceupload",
559 description = "Uploading source archives",
560 workdir = "build/sdk",
561 command = ["rsync", "--files-from=sourcelist", "-4", "--progress", "--checksum", "--delay-updates",
562 WithProperties("--partial-dir=.~tmp~%s~%%(slavename)s" %(arch[0])), "-avz", "dl/", "%s/" %(rsync_src_url)],
563 env={'RSYNC_PASSWORD': rsync_src_key},
564 haltOnFailure = False,
565 logEnviron = False
566 ))
567
568 factory.addStep(ShellCommand(
569 name = "df",
570 description = "Reporting disk usage",
571 command=["df", "-h", "."],
572 env={'LC_ALL': 'C'},
573 haltOnFailure = False,
574 alwaysRun = True
575 ))
576
577 from buildbot.config import BuilderConfig
578
579 c['builders'].append(BuilderConfig(name=arch[0], slavenames=slaveNames, factory=factory))
580
581
582 ####### STATUS arches
583
584 # 'status' is a list of Status arches. The results of each build will be
585 # pushed to these arches. buildbot/status/*.py has a variety to choose from,
586 # including web pages, email senders, and IRC bots.
587
588 c['status'] = []
589
590 from buildbot.status import html
591 from buildbot.status.web import authz, auth
592
593 if ini.has_option("phase2", "status_bind"):
594 if ini.has_option("phase2", "status_user") and ini.has_option("phase2", "status_password"):
595 authz_cfg=authz.Authz(
596 # change any of these to True to enable; see the manual for more
597 # options
598 auth=auth.BasicAuth([(ini.get("phase2", "status_user"), ini.get("phase2", "status_password"))]),
599 gracefulShutdown = 'auth',
600 forceBuild = 'auth', # use this to test your slave once it is set up
601 forceAllBuilds = 'auth',
602 pingBuilder = False,
603 stopBuild = 'auth',
604 stopAllBuilds = 'auth',
605 cancelPendingBuild = 'auth',
606 )
607 c['status'].append(html.WebStatus(http_port=ini.get("phase2", "status_bind"), authz=authz_cfg))
608 else:
609 c['status'].append(html.WebStatus(http_port=ini.get("phase2", "status_bind")))
610
611 ####### PROJECT IDENTITY
612
613 # the 'title' string will appear at the top of this buildbot
614 # installation's html.WebStatus home page (linked to the
615 # 'titleURL') and is embedded in the title of the waterfall HTML page.
616
617 c['title'] = ini.get("general", "title")
618 c['titleURL'] = ini.get("general", "title_url")
619
620 # the 'buildbotURL' string should point to the location where the buildbot's
621 # internal web server (usually the html.WebStatus page) is visible. This
622 # typically uses the port number set in the Waterfall 'status' entry, but
623 # with an externally-visible host name which the buildbot cannot figure out
624 # without some help.
625
626 c['buildbotURL'] = buildbot_url
627
628 ####### DB URL
629
630 c['db'] = {
631 # This specifies what database buildbot uses to store its state. You can leave
632 # this at its default for all but the largest installations.
633 'db_url' : "sqlite:///state.sqlite",
634 }