2 # ex: set syntax=python:
10 from buildbot import locks
12 ini = ConfigParser.ConfigParser()
13 ini.read("./config.ini")
15 buildbot_url = ini.get("general", "buildbot_url")
17 # This is a sample buildmaster config file. It must be installed as
18 # 'master.cfg' in your buildmaster's base directory.
20 # This is the dictionary that the buildmaster pays attention to. We also use
21 # a shorter alias to save typing.
22 c = BuildmasterConfig = {}
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
38 if ini.has_option("general", "port"):
39 slave_port = ini.getint("general", "port")
41 if ini.has_option("general", "persistent"):
42 persistent = ini.getboolean("general", "persistent")
44 if ini.has_option("general", "other_builds"):
45 other_builds = ini.getint("general", "other_builds")
47 if ini.has_option("general", "expire"):
48 tree_expire = ini.getint("general", "expire")
50 if ini.has_option("general", "git_ssh"):
51 git_ssh = ini.getboolean("general", "git_ssh")
53 if ini.has_option("general", "git_ssh_key"):
54 git_ssh_key = ini.get("general", "git_ssh_key")
61 for section in ini.sections():
62 if section.startswith("slave "):
63 if ini.has_option(section, "name") and ini.has_option(section, "password"):
64 name = ini.get(section, "name")
65 password = ini.get(section, "password")
67 if ini.has_option(section, "builds"):
68 max_builds[name] = ini.getint(section, "builds")
69 c['slaves'].append(BuildSlave(name, password, max_builds = max_builds[name]))
71 # 'slavePortnum' defines the TCP port to listen on for connections from slaves.
72 # This must match the value configured into the buildslaves (with their
74 c['slavePortnum'] = slave_port
77 c['mergeRequests'] = True
79 # Reduce amount of backlog data
80 c['buildHorizon'] = 30
85 home_dir = os.path.abspath(ini.get("general", "homedir"))
86 scripts_dir = os.path.abspath("../scripts")
88 rsync_bin_url = ini.get("rsync", "binary_url")
89 rsync_bin_key = ini.get("rsync", "binary_password")
94 if ini.has_option("rsync", "source_url"):
95 rsync_src_url = ini.get("rsync", "source_url")
96 rsync_src_key = ini.get("rsync", "source_password")
100 rsync_sdk_pat = "openwrt-sdk-*.tar.xz"
102 if ini.has_option("rsync", "sdk_url"):
103 rsync_sdk_url = ini.get("rsync", "sdk_url")
105 if ini.has_option("rsync", "sdk_password"):
106 rsync_sdk_key = ini.get("rsync", "sdk_password")
108 if ini.has_option("rsync", "sdk_pattern"):
109 rsync_sdk_pat = ini.get("rsync", "sdk_pattern")
111 repo_url = ini.get("repo", "url")
112 repo_branch = "master"
114 if ini.has_option("repo", "branch"):
115 repo_branch = ini.get("repo", "branch")
118 gpg_passphrase = None
119 gpg_comment = repo_branch.replace("-", " ").title() + " key"
121 if ini.has_option("gpg", "key"):
122 gpg_key = ini.get("gpg", "key")
124 if ini.has_option("gpg", "passphrase"):
125 gpg_passphrase = ini.get("gpg", "passphrase")
127 if ini.has_option("gpg", "comment"):
128 gpg_comment = ini.get("gpg", "comment")
131 usign_comment = "untrusted comment: " + repo_branch.replace("-", " ").title() + " key"
133 if ini.has_option("usign", "key"):
134 usign_key = ini.get("usign", "key")
136 if ini.has_option("usign", "comment"):
137 usign_comment = ini.get("usign", "comment")
144 if not os.path.isdir(home_dir+'/source.git'):
145 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, home_dir+'/source.git'])
147 subprocess.call(["git", "pull"], cwd = home_dir+'/source.git')
149 findarches = subprocess.Popen([scripts_dir + '/dumpinfo.pl', 'architectures'],
150 stdout = subprocess.PIPE, cwd = home_dir+'/source.git')
153 line = findarches.stdout.readline()
156 at = line.strip().split()
158 archnames.append(at[0])
163 feedbranches = dict()
165 from buildbot.changes.gitpoller import GitPoller
166 c['change_source'] = []
168 def parse_feed_entry(line):
169 parts = line.strip().split()
170 if parts[0] == "src-git":
172 url = parts[2].strip().split(';')
173 branch = url[1] if len(url) > 1 else 'master'
174 feedbranches[url[0]] = branch
175 c['change_source'].append(GitPoller(url[0], branch=branch, workdir='%s/%s.git' %(os.getcwd(), parts[1]), pollinterval=300))
177 make = subprocess.Popen(['make', '--no-print-directory', '-C', home_dir+'/source.git/target/sdk/', 'val.BASE_FEED'],
178 env = dict(os.environ, TOPDIR=home_dir+'/source.git'), stdout = subprocess.PIPE)
180 line = make.stdout.readline()
182 parse_feed_entry(line)
184 with open(home_dir+'/source.git/feeds.conf.default', 'r') as f:
186 parse_feed_entry(line)
191 # Configure the Schedulers, which decide how to react to incoming changes. In this
192 # case, just kick off a 'basebuild' build
194 def branch_change_filter(change):
195 return change.branch == feedbranches[change.repository]
197 from buildbot.schedulers.basic import SingleBranchScheduler
198 from buildbot.schedulers.forcesched import ForceScheduler
199 from buildbot.changes import filter
201 c['schedulers'].append(SingleBranchScheduler(
203 change_filter=filter.ChangeFilter(filter_fn=branch_change_filter),
205 builderNames=archnames))
207 c['schedulers'].append(ForceScheduler(
209 builderNames=archnames))
213 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
214 # what steps, and which slaves can execute them. Note that any particular build will
215 # only take place on one slave.
217 from buildbot.process.factory import BuildFactory
218 from buildbot.steps.source import Git
219 from buildbot.steps.shell import ShellCommand
220 from buildbot.steps.shell import SetProperty
221 from buildbot.steps.transfer import FileUpload
222 from buildbot.steps.transfer import FileDownload
223 from buildbot.steps.transfer import StringDownload
224 from buildbot.steps.master import MasterShellCommand
225 from buildbot.process.properties import WithProperties
228 def GetDirectorySuffix(props):
229 verpat = re.compile('^([0-9]{2})\.([0-9]{2})(?:\.([0-9]+)(?:-rc([0-9]+))?|-(SNAPSHOT))$')
230 if props.hasProperty("release_version"):
231 m = verpat.match(props["release_version"])
233 return "-%02d.%02d" %(int(m.group(1)), int(m.group(2)))
236 def GetNumJobs(props):
237 if props.hasProperty("slavename") and props.hasProperty("nproc"):
238 return ((int(props["nproc"]) / (max_builds[props["slavename"]] + other_builds)) + 1)
243 if props.hasProperty("builddir"):
244 return props["builddir"]
245 elif props.hasProperty("workdir"):
246 return props["workdir"]
250 def UsignSec2Pub(seckey, comment="untrusted comment: secret key"):
252 seckey = base64.b64decode(seckey)
256 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
257 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
262 dlLock = locks.SlaveLock("slave_dl")
266 for slave in c['slaves']:
267 slaveNames.append(slave.slavename)
270 ts = arch[1].split('/')
272 factory = BuildFactory()
274 # find number of cores
275 factory.addStep(SetProperty(
278 description = "Finding number of CPUs",
279 command = ["nproc"]))
282 factory.addStep(FileDownload(mastersrc="cleanup.sh", slavedest="cleanup.sh", mode=0755))
285 factory.addStep(ShellCommand(
287 description = "Cleaning previous builds",
288 command = ["./cleanup.sh", buildbot_url, WithProperties("%(slavename)s"), WithProperties("%(buildername)s"), "full"],
289 haltOnFailure = True,
292 factory.addStep(ShellCommand(
294 description = "Cleaning work area",
295 command = ["./cleanup.sh", buildbot_url, WithProperties("%(slavename)s"), WithProperties("%(buildername)s"), "single"],
296 haltOnFailure = True,
299 # expire tree if needed
300 elif tree_expire > 0:
301 factory.addStep(FileDownload(
302 mastersrc = scripts_dir + '/expire.sh',
303 slavedest = "../expire.sh",
306 factory.addStep(ShellCommand(
308 description = "Checking for build tree expiry",
309 command = ["./expire.sh", str(tree_expire)],
311 haltOnFailure = True,
314 factory.addStep(ShellCommand(
316 description = "Preparing SDK directory",
317 command = ["mkdir", "-p", "sdk"],
318 haltOnFailure = True))
320 factory.addStep(ShellCommand(
321 name = "downloadsdk",
322 description = "Downloading SDK archive",
323 command = ["rsync", "-4", "-va", "%s/%s/%s/%s" %(rsync_sdk_url, ts[0], ts[1], rsync_sdk_pat), "sdk.archive"],
324 env={'RSYNC_PASSWORD': rsync_sdk_key},
325 haltOnFailure = True,
328 factory.addStep(ShellCommand(
330 description = "Unpacking SDK archive",
331 command = "rm -rf sdk_update && mkdir sdk_update && tar --strip-components=1 -C sdk_update/ -vxf sdk.archive",
332 haltOnFailure = True))
334 factory.addStep(ShellCommand(
336 description = "Updating SDK",
337 command = "rsync --checksum -av sdk_update/ sdk/ && rm -rf sdk_update",
338 haltOnFailure = True))
340 factory.addStep(StringDownload(
341 name = "writeversionmk",
342 s = 'TOPDIR:=${CURDIR}\n\ninclude $(TOPDIR)/include/version.mk\n\nversion:\n\t@echo $(VERSION_NUMBER)\n',
343 slavedest = "sdk/getversion.mk",
346 factory.addStep(SetProperty(
348 property = "release_version",
349 description = "Finding SDK release version",
350 workdir = "build/sdk",
351 command = ["make", "-f", "getversion.mk"]))
354 if usign_key is not None:
355 factory.addStep(StringDownload(
356 name = "dlkeybuildpub",
357 s = UsignSec2Pub(usign_key, usign_comment),
358 slavedest = "sdk/key-build.pub",
361 factory.addStep(StringDownload(
363 s = "# fake private key",
364 slavedest = "sdk/key-build",
367 factory.addStep(StringDownload(
368 name = "dlkeybuilducert",
369 s = "# fake certificate",
370 slavedest = "sdk/key-build.ucert",
373 factory.addStep(ShellCommand(
375 description = "Preparing download directory",
376 command = ["sh", "-c", "mkdir -p $HOME/dl && rm -rf ./sdk/dl && ln -sf $HOME/dl ./sdk/dl"],
377 haltOnFailure = True))
379 factory.addStep(ShellCommand(
381 description = "Preparing SDK configuration",
382 workdir = "build/sdk",
383 command = ["sh", "-c", "rm -f .config && make defconfig"]))
385 factory.addStep(FileDownload(
386 mastersrc = scripts_dir + '/ccache.sh',
387 slavedest = 'sdk/ccache.sh',
390 factory.addStep(ShellCommand(
392 description = "Preparing ccache",
393 workdir = "build/sdk",
394 command = ["./ccache.sh"],
395 haltOnFailure = True))
398 factory.addStep(StringDownload(
399 name = "dlgitclonekey",
401 slavedest = "../git-clone.key",
404 factory.addStep(ShellCommand(
405 name = "patchfeedsconf",
406 description = "Patching feeds.conf",
407 workdir = "build/sdk",
408 command = "sed -e 's#https://#ssh://git@#g' feeds.conf.default > feeds.conf",
409 haltOnFailure = True))
411 factory.addStep(ShellCommand(
412 name = "updatefeeds",
413 description = "Updating feeds",
414 workdir = "build/sdk",
415 command = ["./scripts/feeds", "update", "-f"],
416 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 {},
417 haltOnFailure = True))
420 factory.addStep(ShellCommand(
421 name = "rmfeedsconf",
422 description = "Removing feeds.conf",
423 workdir = "build/sdk",
424 command=["rm", "feeds.conf"],
425 haltOnFailure = True))
427 factory.addStep(ShellCommand(
428 name = "installfeeds",
429 description = "Installing feeds",
430 workdir = "build/sdk",
431 command = ["./scripts/feeds", "install", "-a"],
432 haltOnFailure = True))
434 factory.addStep(ShellCommand(
436 description = "Clearing failure logs",
437 workdir = "build/sdk",
438 command = ["rm", "-rf", "logs/package/error.txt", "faillogs/"],
439 haltOnFailure = False
442 factory.addStep(ShellCommand(
444 description = "Building packages",
445 workdir = "build/sdk",
447 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "IGNORE_ERRORS=n m y", "BUILD_LOG=1", "CONFIG_AUTOREMOVE=y"],
448 env = {'CCACHE_BASEDIR': WithProperties("%(cwd)s", cwd=GetCwd)},
449 haltOnFailure = True))
451 factory.addStep(ShellCommand(
452 name = "mkfeedsconf",
453 description = "Generating pinned feeds.conf",
454 workdir = "build/sdk",
455 command = "./scripts/feeds list -s -f > bin/packages/%s/feeds.conf" %(arch[0])))
457 if gpg_key is not None or usign_key is not None:
458 factory.addStep(MasterShellCommand(
459 name = "signprepare",
460 description = "Preparing temporary signing directory",
461 command = ["mkdir", "-p", "%s/signing" %(home_dir)],
465 factory.addStep(ShellCommand(
467 description = "Packing files to sign",
468 workdir = "build/sdk",
469 command = "find bin/packages/%s/ -mindepth 2 -maxdepth 2 -type f -name Packages -print0 | xargs -0 tar -czf sign.tar.gz" %(arch[0]),
473 factory.addStep(FileUpload(
474 slavesrc = "sdk/sign.tar.gz",
475 masterdest = "%s/signing/%s.tar.gz" %(home_dir, arch[0]),
479 factory.addStep(MasterShellCommand(
481 description = "Signing files",
482 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.tar.gz" %(home_dir, arch[0])],
485 'GPGPASS': gpg_passphrase,
486 'GPGCOMMENT': gpg_comment,
487 'USIGNKEY': usign_key,
488 'USIGNCOMMENT': usign_comment
493 factory.addStep(FileDownload(
494 mastersrc = "%s/signing/%s.tar.gz" %(home_dir, arch[0]),
495 slavedest = "sdk/sign.tar.gz",
499 factory.addStep(ShellCommand(
501 description = "Unpacking signed files",
502 workdir = "build/sdk",
503 command = ["tar", "-xzf", "sign.tar.gz"],
507 factory.addStep(ShellCommand(
508 name = "uploadprepare",
509 description = "Preparing package directory",
510 workdir = "build/sdk",
511 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)],
512 env={'RSYNC_PASSWORD': rsync_bin_key},
513 haltOnFailure = True,
517 factory.addStep(ShellCommand(
518 name = "packageupload",
519 description = "Uploading package files",
520 workdir = "build/sdk",
521 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)],
522 env={'RSYNC_PASSWORD': rsync_bin_key},
523 haltOnFailure = True,
527 factory.addStep(ShellCommand(
529 description = "Preparing log directory",
530 workdir = "build/sdk",
531 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)],
532 env={'RSYNC_PASSWORD': rsync_bin_key},
533 haltOnFailure = True,
537 factory.addStep(ShellCommand(
539 description = "Finding failure logs",
540 workdir = "build/sdk/logs/package/feeds",
541 command = ["sh", "-c", "sed -ne 's!^ *ERROR: package/feeds/\\([^ ]*\\) .*$!\\1!p' ../error.txt | sort -u | xargs -r find > ../../../logs.txt"],
542 haltOnFailure = False
545 factory.addStep(ShellCommand(
547 description = "Collecting failure logs",
548 workdir = "build/sdk",
549 command = ["rsync", "-av", "--files-from=logs.txt", "logs/package/feeds/", "faillogs/"],
550 haltOnFailure = False
553 factory.addStep(ShellCommand(
555 description = "Uploading failure logs",
556 workdir = "build/sdk",
557 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)],
558 env={'RSYNC_PASSWORD': rsync_bin_key},
559 haltOnFailure = False,
563 if rsync_src_url is not None:
564 factory.addStep(ShellCommand(
566 description = "Finding source archives to upload",
567 workdir = "build/sdk",
568 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -newer ../sdk.archive -printf '%f\\n' > sourcelist",
572 factory.addStep(ShellCommand(
573 name = "sourceupload",
574 description = "Uploading source archives",
575 workdir = "build/sdk",
576 command = ["rsync", "--files-from=sourcelist", "-4", "--progress", "--checksum", "--delay-updates",
577 WithProperties("--partial-dir=.~tmp~%s~%%(slavename)s" %(arch[0])), "-avz", "dl/", "%s/" %(rsync_src_url)],
578 env={'RSYNC_PASSWORD': rsync_src_key},
579 haltOnFailure = False,
583 factory.addStep(ShellCommand(
585 description = "Reporting disk usage",
586 command=["df", "-h", "."],
588 haltOnFailure = False,
592 from buildbot.config import BuilderConfig
594 c['builders'].append(BuilderConfig(name=arch[0], slavenames=slaveNames, factory=factory))
597 ####### STATUS arches
599 # 'status' is a list of Status arches. The results of each build will be
600 # pushed to these arches. buildbot/status/*.py has a variety to choose from,
601 # including web pages, email senders, and IRC bots.
605 from buildbot.status import html
606 from buildbot.status.web import authz, auth
608 if ini.has_option("status", "bind"):
609 if ini.has_option("status", "user") and ini.has_option("status", "password"):
610 authz_cfg=authz.Authz(
611 # change any of these to True to enable; see the manual for more
613 auth=auth.BasicAuth([(ini.get("status", "user"), ini.get("status", "password"))]),
614 gracefulShutdown = 'auth',
615 forceBuild = 'auth', # use this to test your slave once it is set up
616 forceAllBuilds = 'auth',
619 stopAllBuilds = 'auth',
620 cancelPendingBuild = 'auth',
622 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind"), authz=authz_cfg))
624 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind")))
626 ####### PROJECT IDENTITY
628 # the 'title' string will appear at the top of this buildbot
629 # installation's html.WebStatus home page (linked to the
630 # 'titleURL') and is embedded in the title of the waterfall HTML page.
632 c['title'] = ini.get("general", "title")
633 c['titleURL'] = ini.get("general", "title_url")
635 # the 'buildbotURL' string should point to the location where the buildbot's
636 # internal web server (usually the html.WebStatus page) is visible. This
637 # typically uses the port number set in the Waterfall 'status' entry, but
638 # with an externally-visible host name which the buildbot cannot figure out
641 c['buildbotURL'] = buildbot_url
646 # This specifies what database buildbot uses to store its state. You can leave
647 # this at its default for all but the largest installations.
648 'db_url' : "sqlite:///state.sqlite",