2 # ex: set syntax=python:
10 from dateutil.tz import tzutc
11 from datetime import datetime, timedelta
13 from twisted.internet import defer
14 from twisted.python import log
16 from buildbot import locks
17 from buildbot.data import resultspec
18 from buildbot.changes.gitpoller import GitPoller
19 from buildbot.config import BuilderConfig
20 from buildbot.plugins import reporters
21 from buildbot.plugins import schedulers
22 from buildbot.plugins import steps
23 from buildbot.plugins import util
24 from buildbot.process import properties
25 from buildbot.process import results
26 from buildbot.process.factory import BuildFactory
27 from buildbot.process.properties import Interpolate
28 from buildbot.process.properties import Property
29 from buildbot.schedulers.basic import AnyBranchScheduler
30 from buildbot.schedulers.forcesched import BaseParameter
31 from buildbot.schedulers.forcesched import ForceScheduler
32 from buildbot.schedulers.forcesched import ValidationError
33 from buildbot.steps.master import MasterShellCommand
34 from buildbot.steps.shell import SetPropertyFromCommand
35 from buildbot.steps.shell import ShellCommand
36 from buildbot.steps.source.git import Git
37 from buildbot.steps.transfer import FileDownload
38 from buildbot.steps.transfer import FileUpload
39 from buildbot.steps.transfer import StringDownload
40 from buildbot.worker import Worker
41 from buildbot.worker.local import LocalWorker
44 if not os.path.exists("twistd.pid"):
45 with open("twistd.pid", "w") as pidfile:
46 pidfile.write("{}".format(os.getpid()))
48 # This is a sample buildmaster config file. It must be installed as
49 # 'master.cfg' in your buildmaster's base directory.
51 ini = configparser.ConfigParser()
52 ini.read(os.getenv("BUILDMASTER_CONFIG", "./config.ini"))
54 if "general" not in ini or "phase1" not in ini:
55 raise ValueError("Fix your configuration")
60 work_dir = os.path.abspath(ini["general"].get("workdir", "."))
61 scripts_dir = os.path.abspath("../scripts")
63 repo_url = ini["repo"].get("url")
65 rsync_defopts = ["-v", "--timeout=120"]
67 # if rsync_bin_url.find("::") > 0 or rsync_bin_url.find("rsync://") == 0:
68 # rsync_bin_defopts += ["--contimeout=20"]
73 def ini_parse_branch(section):
75 name = section.get("name")
78 raise ValueError("missing 'name' in " + repr(section))
80 raise ValueError("duplicate branch name in " + repr(section))
83 b["bin_url"] = section.get("binary_url")
84 b["bin_key"] = section.get("binary_password")
86 b["src_url"] = section.get("source_url")
87 b["src_key"] = section.get("source_password")
89 b["gpg_key"] = section.get("gpg_key")
91 b["usign_key"] = section.get("usign_key")
92 usign_comment = "untrusted comment: " + name.replace("-", " ").title() + " key"
93 b["usign_comment"] = section.get("usign_comment", usign_comment)
95 b["config_seed"] = section.get("config_seed")
97 b["kmod_archive"] = section.getboolean("kmod_archive", False)
100 log.msg("Configured branch: {}".format(name))
103 # PB port can be either a numeric port or a connection string
104 pb_port = inip1.get("port") or 9989
106 # This is the dictionary that the buildmaster pays attention to. We also use
107 # a shorter alias to save typing.
108 c = BuildmasterConfig = {}
110 ####### PROJECT IDENTITY
112 # the 'title' string will appear at the top of this buildbot
113 # installation's html.WebStatus home page (linked to the
114 # 'titleURL') and is embedded in the title of the waterfall HTML page.
116 c["title"] = ini["general"].get("title")
117 c["titleURL"] = ini["general"].get("title_url")
119 # the 'buildbotURL' string should point to the location where the buildbot's
120 # internal web server (usually the html.WebStatus page) is visible. This
121 # typically uses the port number set in the Waterfall 'status' entry, but
122 # with an externally-visible host name which the buildbot cannot figure out
125 c["buildbotURL"] = inip1.get("buildbot_url")
129 # The 'workers' list defines the set of recognized buildworkers. Each element is
130 # a Worker object, specifying a unique worker name and password. The same
131 # worker name and password must be configured on the worker.
137 def ini_parse_workers(section):
138 name = section.get("name")
139 password = section.get("password")
140 phase = section.getint("phase")
141 tagonly = section.getboolean("tag_only")
142 rsyncipv4 = section.getboolean("rsync_ipv4")
144 if not name or not password or not phase == 1:
145 log.msg("invalid worker configuration ignored: {}".format(repr(section)))
148 sl_props = {"tag_only": tagonly}
149 if "dl_lock" in section:
150 lockname = section.get("dl_lock")
151 sl_props["dl_lock"] = lockname
152 if lockname not in NetLocks:
153 NetLocks[lockname] = locks.MasterLock(lockname)
154 if "ul_lock" in section:
155 lockname = section.get("ul_lock")
156 sl_props["ul_lock"] = lockname
157 if lockname not in NetLocks:
158 NetLocks[lockname] = locks.MasterLock(lockname)
162 ] = True # only set prop if required, we use '+' Interpolate substitution
164 log.msg("Configured worker: {}".format(name))
165 # NB: phase1 build factory requires workers to be single-build only
166 c["workers"].append(Worker(name, password, max_builds=1, properties=sl_props))
169 for section in ini.sections():
170 if section.startswith("branch "):
171 ini_parse_branch(ini[section])
173 if section.startswith("worker "):
174 ini_parse_workers(ini[section])
176 # list of branches in build-priority order
177 branchNames = [branches[b]["name"] for b in branches]
179 c["protocols"] = {"pb": {"port": pb_port}}
182 c["collapseRequests"] = True
184 # Reduce amount of backlog data
185 c["configurators"] = [
186 util.JanitorConfigurator(
187 logHorizon=timedelta(days=3),
193 @defer.inlineCallbacks
194 def getNewestCompleteTimePrio(bldr):
195 """Returns the priority and the complete_at of the latest completed and not SKIPPED
196 build request for this builder, or None if there are no such build
197 requests. We need to filter out SKIPPED requests because we're
198 using collapseRequests=True which is unfortunately marking all
199 previous requests as complete when new buildset is created.
201 @returns: (priority, datetime instance or None), via Deferred
204 prio = yield bldr.get_highest_priority()
208 bldrid = yield bldr.getBuilderId()
209 completed = yield bldr.master.data.get(
210 ("builders", bldrid, "buildrequests"),
212 resultspec.Filter("complete", "eq", [True]),
213 resultspec.Filter("results", "ne", [results.SKIPPED]),
215 order=["-complete_at"],
221 complete_at = completed[0]["complete_at"]
223 last_build = yield bldr.master.data.get(
226 resultspec.Filter("builderid", "eq", [bldrid]),
228 order=["-started_at"],
232 if last_build and last_build[0]:
233 last_complete_at = last_build[0]["complete_at"]
234 if last_complete_at and (last_complete_at > complete_at):
235 return (prio, last_complete_at)
237 return (prio, complete_at)
240 @defer.inlineCallbacks
241 def prioritizeBuilders(master, builders):
242 """Returns sorted list of builders by their last timestamp of completed and
243 not skipped build, ordered first by branch name.
245 @returns: list of sorted builders
248 bldrNamePrio = {"__Janitor": 0, "00_force_build": 0}
250 for bname in branchNames:
251 bldrNamePrio[bname] = i
253 def is_building(bldr):
254 return bool(bldr.building) or bool(bldr.old_building)
257 d = defer.maybeDeferred(getNewestCompleteTimePrio, bldr)
258 d.addCallback(lambda retval: (retval, bldr))
262 ((hiprio, complete_at), bldr) = item
264 # check if we have some high prio build requests pending (i.e. tag builds),
265 # if so, front-run these builders, while preserving the per-branch static priority
267 for name, prio in bldrNamePrio.items():
268 if bldr.name.startswith(name):
269 pos = prio + 50 - min(hiprio, 50) # higher priority (larger positive number) raises position
272 # pos order: janitor/local (0), tag builds if any [1..50], !tag builds [51...]
276 complete_at = date.replace(tzinfo=tzutc())
278 if is_building(bldr):
280 complete_at = date.replace(tzinfo=tzutc())
282 return (pos, complete_at, bldr.name)
284 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
285 results.sort(key=bldr_sort)
288 # log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
290 return [r[1] for r in results]
293 c["prioritizeBuilders"] = prioritizeBuilders
295 ####### CHANGESOURCES
301 def populateTargets():
302 """fetch a shallow clone of each configured branch in turn:
303 execute dump-target-info.pl and collate the results to ensure
304 targets that only exist in specific branches get built.
305 This takes a while during master startup but is executed only once.
307 sourcegit = work_dir + "/source.git"
308 for branch in branchNames:
309 log.msg(f"Populating targets for {branch}, this will take time")
311 if os.path.isdir(sourcegit):
312 subprocess.call(["rm", "-rf", sourcegit])
320 "--branch=" + branch,
326 os.makedirs(sourcegit + "/tmp", exist_ok=True)
327 findtargets = subprocess.Popen(
328 ["./scripts/dump-target-info.pl", "targets"],
329 stdout=subprocess.PIPE,
330 stderr=subprocess.DEVNULL,
334 targets[branch] = set()
336 line = findtargets.stdout.readline()
339 ta = line.decode().strip().split(" ")
340 targets[branch].add(ta[0])
342 subprocess.call(["rm", "-rf", sourcegit])
347 # the 'change_source' setting tells the buildmaster how it should find out
348 # about source code changes.
350 c["change_source"] = []
351 c["change_source"].append(
354 workdir=work_dir + "/work.git",
355 branches=branchNames,
363 # Configure the Schedulers, which decide how to react to incoming changes.
366 # Selector for known valid tags
367 class TagChoiceParameter(BaseParameter):
368 spec_attributes = ["strict", "choices"]
372 def __init__(self, name, label=None, **kw):
373 super().__init__(name, label, **kw)
374 self._choice_list = []
376 def getRevTags(self, findtag=None):
380 # we will filter out tags that do no match the configured branches
381 for b in branchNames:
382 basever = re.search(r"-([0-9]+\.[0-9]+)$", b)
384 branchvers.append(basever[1])
386 # grab tags from remote repository
387 alltags = subprocess.Popen(
388 ["git", "ls-remote", "--tags", repo_url], stdout=subprocess.PIPE
392 line = alltags.stdout.readline()
397 (rev, tag) = line.split()
399 # does it match known format? ('vNN.NN.NN(-rcN)')
401 r"\brefs/tags/(v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$",
402 tag.decode().strip(),
405 # only list valid tags matching configured branches
406 if tagver and any(tagver[1][1:].startswith(b) for b in branchvers):
407 # if we want a specific tag, ignore all that don't match
408 if findtag and findtag != tagver[1]:
410 taglist.append({"rev": rev.decode().strip(), "tag": tagver[1]})
416 taglist = [rt["tag"] for rt in self.getRevTags()]
419 key=lambda tag: tag if re.search(r"-rc[0-9]+$", tag) else tag + "-z",
421 taglist.insert(0, "")
423 self._choice_list = taglist
425 return self._choice_list
427 def updateFromKwargs(self, properties, kwargs, **unused):
428 tag = self.getFromKwargs(kwargs)
429 properties[self.name] = tag
431 # find the commit matching the tag
432 findtag = self.getRevTags(tag)
435 raise ValidationError("Couldn't find tag")
437 properties["force_revision"] = findtag[0]["rev"]
439 # find the branch matching the tag
441 branchver = re.search(r"v([0-9]+\.[0-9]+)", tag)
442 for b in branchNames:
443 if b.endswith(branchver[1]):
447 raise ValidationError("Couldn't find branch")
449 properties["force_branch"] = branch
451 def parse_from_arg(self, s):
452 if self.strict and s not in self._choice_list:
453 raise ValidationError(
454 "'%s' does not belong to list of available choices '%s'"
455 % (s, self._choice_list)
461 @defer.inlineCallbacks
462 def builderNames(props):
463 """since we have per branch and per target builders,
464 address the relevant builder for each new buildrequest
465 based on the request's desired branch and target.
467 branch = props.getProperty("branch")
468 target = props.getProperty("target", "")
473 # if that didn't work, try sourcestamp to find a branch
475 # match builders with target branch
476 ss = props.sourcestamps[0]
478 branch = ss["branch"]
480 log.msg("couldn't find builder")
481 return [] # nothing works
483 bname = branch + "_" + target
486 for b in (yield props.master.data.get(("builders",))):
487 if not b["name"].startswith(bname):
489 builders.append(b["name"])
495 c["schedulers"].append(
498 change_filter=util.ChangeFilter(branch=branchNames),
499 treeStableTimer=15 * 60,
500 builderNames=builderNames,
504 c["schedulers"].append(
507 buttonName="Force builds",
508 label="Force build details",
509 builderNames=["00_force_build"],
511 util.CodebaseParameter(
514 branch=util.FixedParameter(name="branch", default=""),
515 revision=util.FixedParameter(name="revision", default=""),
516 repository=util.FixedParameter(name="repository", default=""),
517 project=util.FixedParameter(name="project", default=""),
520 reason=util.StringParameter(
523 default="Trigger build",
528 # NB: avoid nesting to simplify processing of properties
529 util.ChoiceStringParameter(
531 label="Build target",
533 choices=["all"] + [t for b in branchNames for t in targets[b]],
535 TagChoiceParameter(name="tag", label="Build tag", default=""),
540 c["schedulers"].append(
541 schedulers.Triggerable(name="trigger", builderNames=builderNames, priority=20)
546 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
547 # what steps, and which workers can execute them. Note that any particular build will
548 # only take place on one worker.
551 def IsNoMasterBuild(step):
552 return step.getProperty("branch") != "master"
555 def IsUsignEnabled(step):
556 branch = step.getProperty("branch")
557 return branch and branches[branch].get("usign_key")
560 def IsSignEnabled(step):
561 branch = step.getProperty("branch")
562 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
565 def IsKmodArchiveEnabled(step):
566 branch = step.getProperty("branch")
567 return branch and branches[branch].get("kmod_archive")
570 def IsKmodArchiveAndRsyncEnabled(step):
571 branch = step.getProperty("branch")
572 return bool(IsKmodArchiveEnabled(step) and branches[branch].get("bin_url"))
575 def GetBaseVersion(branch):
576 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
577 return branch.split("-")[1]
583 def GetVersionPrefix(props):
584 branch = props.getProperty("branch")
585 basever = GetBaseVersion(branch)
586 if props.hasProperty("tag") and re.match(
587 r"^v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]
589 return "%s/" % props["tag"][1:]
590 elif basever != "master":
591 return "%s-SNAPSHOT/" % basever
597 def GetConfigSeed(props):
598 branch = props.getProperty("branch")
599 return branch and branches[branch].get("config_seed") or ""
603 def GetRsyncParams(props, srcorbin, urlorkey):
604 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
605 branch = props.getProperty("branch")
606 opt = srcorbin + "_" + urlorkey
607 return branch and branches[branch].get(opt)
611 def GetUsignKey(props):
612 branch = props.getProperty("branch")
613 return branch and branches[branch].get("usign_key")
616 def GetNextBuild(builder, requests):
619 # order tagged build first
620 if r.properties.hasProperty("tag"):
624 # log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
628 def MakeEnv(overrides=None, tryccache=False):
630 "CCC": Interpolate("%(prop:cc_command:-gcc)s"),
631 "CCXX": Interpolate("%(prop:cxx_command:-g++)s"),
634 env["CC"] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
635 env["CXX"] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
636 env["CCACHE"] = Interpolate("%(prop:ccache_command:-)s")
638 env["CC"] = env["CCC"]
639 env["CXX"] = env["CCXX"]
641 if overrides is not None:
642 env.update(overrides)
647 def NetLockDl(props, extralock=None):
649 if props.hasProperty("dl_lock"):
650 lock = NetLocks[props["dl_lock"]]
652 return [lock.access("exclusive")]
658 def NetLockUl(props):
660 if props.hasProperty("ul_lock"):
661 lock = NetLocks[props["ul_lock"]]
663 return [lock.access("exclusive")]
668 def IsTargetSelected(target):
669 def CheckTargetProperty(step):
670 selected_target = step.getProperty("target", "all")
671 if selected_target != "all" and selected_target != target:
675 return CheckTargetProperty
679 def UsignSec2Pub(props):
680 branch = props.getProperty("branch")
683 branches[branch].get("usign_comment") or "untrusted comment: secret key"
685 seckey = branches[branch].get("usign_key")
686 seckey = base64.b64decode(seckey)
690 return "{}\n{}".format(
691 re.sub(r"\bsecret key$", "public key", comment),
692 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]),
696 def canStartBuild(builder, wfb, request):
697 """filter out non tag requests for tag_only workers."""
698 wtagonly = wfb.worker.properties.getProperty("tag_only")
699 tag = request.properties.getProperty("tag")
701 if wtagonly and not tag:
711 for worker in c["workers"]:
712 workerNames.append(worker.workername)
714 # add a single LocalWorker to handle the forcebuild builder
715 c["workers"].append(LocalWorker("__local_force_build", max_builds=1))
717 force_factory = BuildFactory()
718 force_factory.addStep(
720 name="trigger_build",
721 schedulerNames=["trigger"],
725 "branch": Property("force_branch"),
726 "revision": Property("force_revision"),
727 "repository": repo_url,
732 "reason": Property("reason"),
733 "tag": Property("tag"),
734 "target": Property("target"),
739 c["builders"].append(
741 name="00_force_build", workername="__local_force_build", factory=force_factory
746 # NB the phase1 build factory assumes workers are single-build only
747 def prepareFactory(target):
748 (target, subtarget) = target.split("/")
750 factory = BuildFactory()
752 # setup shared work directory if required
756 descriptionDone="Shared work directory set up",
757 command='test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
763 # find number of cores
765 SetPropertyFromCommand(
768 description="Finding number of CPUs",
773 # find gcc and g++ compilers
777 mastersrc=scripts_dir + "/findbin.pl",
778 workerdest="../findbin.pl",
784 SetPropertyFromCommand(
786 property="cc_command",
787 description="Finding gcc command",
788 command=["../findbin.pl", "gcc", "", ""],
794 SetPropertyFromCommand(
796 property="cxx_command",
797 description="Finding g++ command",
798 command=["../findbin.pl", "g++", "", ""],
803 # see if ccache is available
805 SetPropertyFromCommand(
807 property="ccache_command",
808 description="Testing for ccache command",
809 command=["which", "ccache"],
811 flunkOnFailure=False,
813 hideStepIf=lambda r, s: r == results.FAILURE,
817 # check out the source
819 # if repo doesn't exist: 'git clone repourl'
820 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -f -f -d -x'. Only works with mode='full'
821 # git cat-file -e <commit>
822 # git checkout -f <commit>
823 # git checkout -B <branch>
836 # workaround for https://github.com/openwrt/buildbot/issues/5
839 name="git me once more please",
852 description="Fetching Git remote refs",
853 descriptionDone="Git remote refs fetched",
859 "+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s"
866 # getver.sh requires local branches to track upstream otherwise version computation fails.
867 # Git() does not set tracking branches when cloning or switching, so work around this here
870 name="trackupstream",
871 description="Setting upstream branch",
872 descriptionDone="getver.sh is happy now",
873 command=["git", "branch", "-u", Interpolate("origin/%(prop:branch)s")],
878 # Verify that Git HEAD points to a tag or branch
879 # Ref: https://web.archive.org/web/20190729224316/http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
883 description="Ensuring that Git HEAD is pointing to a branch or tag",
884 descriptionDone="Git HEAD is sane",
885 command='git rev-parse --abbrev-ref HEAD | grep -vxqF HEAD || git show-ref --tags --dereference 2>/dev/null | sed -ne "/^$(git rev-parse HEAD) / { s|^.*/||; s|\\^.*||; p }" | grep -qE "^v[0-9][0-9]\\."',
893 s='#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
894 workerdest="../ccache_cc.sh",
902 s='#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
903 workerdest="../ccache_cxx.sh",
912 description="Updating feeds",
913 command=["./scripts/feeds", "update"],
914 env=MakeEnv(tryccache=True),
924 description="Installing feeds",
925 command=["./scripts/feeds", "install", "-a"],
926 env=MakeEnv(tryccache=True),
935 s=Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
936 workerdest=".config",
945 descriptionDone=".config seeded",
947 "printf 'CONFIG_TARGET_%(kw:target)s=y\\nCONFIG_TARGET_%(kw:target)s_%(kw:subtarget)s=y\\nCONFIG_SIGNED_PACKAGES=%(kw:usign:#?|y|n)s\\n' >> .config",
958 description="Populating .config",
959 command=["make", "defconfig"],
964 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
968 description="Checking architecture",
969 descriptionDone="Architecture validated",
970 command='grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config'
971 % (target, subtarget),
976 flunkOnFailure=False, # this is not a build FAILURE - TODO mark build as SKIPPED
982 SetPropertyFromCommand(
985 description="Finding libc suffix",
989 '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }',
998 name="dlkeybuildpub",
999 s=Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
1000 workerdest="key-build.pub",
1002 doStepIf=IsUsignEnabled,
1009 s="# fake private key",
1010 workerdest="key-build",
1012 doStepIf=IsUsignEnabled,
1018 name="dlkeybuilducert",
1019 s="# fake certificate",
1020 workerdest="key-build.ucert",
1022 doStepIf=IsUsignEnabled,
1030 description="Preparing dl/",
1031 descriptionDone="dl/ prepared",
1032 command='mkdir -p ../dl && rm -rf "build/dl" && ln -s ../../dl "build/dl"',
1033 workdir=Property("builddir"),
1043 description="Pruning dl/",
1044 descriptionDone="dl/ pruned",
1045 command="find dl/ -mindepth 1 -atime +15 -delete -print",
1047 haltOnFailure=False,
1048 flunkOnFailure=False,
1049 warnOnFailure=False,
1057 description="Building and installing GNU tar",
1058 descriptionDone="GNU tar built and installed",
1061 Interpolate("-j%(prop:nproc:-1)s"),
1062 "tools/tar/compile",
1065 env=MakeEnv(tryccache=True),
1074 description="Populating dl/",
1075 descriptionDone="dl/ populated",
1076 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
1086 description="Cleaning base-files",
1087 command=["make", "package/base-files/clean", "V=s"],
1095 description="Building and installing tools",
1096 descriptionDone="Tools built and installed",
1099 Interpolate("-j%(prop:nproc:-1)s"),
1103 env=MakeEnv(tryccache=True),
1111 description="Building and installing toolchain",
1112 descriptionDone="Toolchain built and installed",
1115 Interpolate("-j%(prop:nproc:-1)s"),
1116 "toolchain/install",
1127 description="Building kmods",
1128 descriptionDone="Kmods built",
1131 Interpolate("-j%(prop:nproc:-1)s"),
1134 "IGNORE_ERRORS=n m",
1142 # find kernel version
1144 SetPropertyFromCommand(
1145 name="kernelversion",
1146 property="kernelversion",
1147 description="Finding the effective Kernel version",
1148 command="make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
1149 env={"TOPDIR": Interpolate("%(prop:builddir)s/build")},
1156 description="Cleaning up package build",
1157 descriptionDone="Package build cleaned up",
1158 command=["make", "package/cleanup", "V=s"],
1165 description="Building packages",
1166 descriptionDone="Packages built",
1169 Interpolate("-j%(prop:nproc:-1)s"),
1172 "IGNORE_ERRORS=n m",
1183 description="Installing packages",
1184 descriptionDone="Packages installed",
1187 Interpolate("-j%(prop:nproc:-1)s"),
1199 description="Indexing packages",
1200 descriptionDone="Packages indexed",
1203 Interpolate("-j%(prop:nproc:-1)s"),
1206 "CONFIG_SIGNED_PACKAGES=",
1216 description="Building and installing images",
1217 descriptionDone="Images built and installed",
1220 Interpolate("-j%(prop:nproc:-1)s"),
1232 description="Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
1233 command="make -j1 buildinfo V=s || true",
1241 name="json_overview_image_info",
1242 description="Generating profiles.json in target folder",
1243 command="make -j1 json_overview_image_info V=s || true",
1252 description="Calculating checksums",
1253 descriptionDone="Checksums calculated",
1254 command=["make", "-j1", "checksum", "V=s"],
1263 descriptionDone="Kmod directory created",
1268 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s",
1270 subtarget=subtarget,
1274 doStepIf=IsKmodArchiveEnabled,
1281 description="Preparing kmod archive",
1282 descriptionDone="Kmod archive prepared",
1285 "--include=/kmod-*.ipk",
1289 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/",
1291 subtarget=subtarget,
1294 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1296 subtarget=subtarget,
1300 doStepIf=IsKmodArchiveEnabled,
1307 description="Indexing kmod archive",
1308 descriptionDone="Kmod archive indexed",
1311 Interpolate("-j%(prop:nproc:-1)s"),
1314 "CONFIG_SIGNED_PACKAGES=",
1316 "PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1318 subtarget=subtarget,
1323 doStepIf=IsKmodArchiveEnabled,
1331 descriptionDone="Temporary signing directory prepared",
1332 command=["mkdir", "-p", "%s/signing" % (work_dir)],
1334 doStepIf=IsSignEnabled,
1341 description="Packing files to sign",
1342 descriptionDone="Files to sign packed",
1343 command=Interpolate(
1344 "find bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/ bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/ -mindepth 1 -maxdepth 2 -type f -name sha256sums -print0 -or -name Packages -print0 | xargs -0 tar -czf sign.tar.gz",
1346 subtarget=subtarget,
1349 doStepIf=IsSignEnabled,
1355 workersrc="sign.tar.gz",
1356 masterdest="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1358 doStepIf=IsSignEnabled,
1365 description="Signing files",
1366 descriptionDone="Files signed",
1368 "%s/signall.sh" % (scripts_dir),
1369 "%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1370 Interpolate("%(prop:branch)s"),
1372 env={"CONFIG_INI": os.getenv("BUILDMASTER_CONFIG", "./config.ini")},
1374 doStepIf=IsSignEnabled,
1381 mastersrc="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1382 workerdest="sign.tar.gz",
1384 doStepIf=IsSignEnabled,
1391 description="Unpacking signed files",
1392 descriptionDone="Signed files unpacked",
1393 command=["tar", "-xzf", "sign.tar.gz"],
1395 doStepIf=IsSignEnabled,
1403 descriptionDone="Upload directory structure prepared",
1408 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s",
1410 subtarget=subtarget,
1411 prefix=GetVersionPrefix,
1421 descriptionDone="Repository symlink prepared",
1427 "../packages-%(kw:basever)s",
1428 basever=util.Transform(GetBaseVersion, Property("branch")),
1431 "tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix
1434 doStepIf=IsNoMasterBuild,
1441 name="kmoddirprepare",
1442 descriptionDone="Kmod archive upload directory prepared",
1447 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s",
1449 subtarget=subtarget,
1450 prefix=GetVersionPrefix,
1454 doStepIf=IsKmodArchiveEnabled,
1461 description="Uploading directory structure",
1462 descriptionDone="Directory structure uploaded",
1463 command=["rsync", Interpolate("-az%(prop:rsync_ipv4:+4)s")]
1467 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url")),
1470 "RSYNC_PASSWORD": Interpolate(
1471 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1477 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1481 # download remote sha256sums to 'target-sha256sums'
1484 name="target-sha256sums",
1485 description="Fetching remote sha256sums for target",
1486 descriptionDone="Remote sha256sums for target fetched",
1487 command=["rsync", Interpolate("-z%(prop:rsync_ipv4:+4)s")]
1491 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/sha256sums",
1492 url=GetRsyncParams.withArgs("bin", "url"),
1494 subtarget=subtarget,
1495 prefix=GetVersionPrefix,
1497 "target-sha256sums",
1500 "RSYNC_PASSWORD": Interpolate(
1501 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1505 haltOnFailure=False,
1506 flunkOnFailure=False,
1507 warnOnFailure=False,
1508 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1512 # build list of files to upload
1515 name="dlsha2rsyncpl",
1516 mastersrc=scripts_dir + "/sha2rsync.pl",
1517 workerdest="../sha2rsync.pl",
1525 description="Building list of files to upload",
1526 descriptionDone="List of files to upload built",
1529 "target-sha256sums",
1531 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1533 subtarget=subtarget,
1544 mastersrc=scripts_dir + "/rsync.sh",
1545 workerdest="../rsync.sh",
1550 # upload new files and update existing ones
1553 name="targetupload",
1554 description="Uploading target files",
1555 descriptionDone="Target files uploaded",
1558 "--exclude=/kmods/",
1559 "--files-from=rsynclist",
1561 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1565 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1567 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1569 subtarget=subtarget,
1572 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1573 url=GetRsyncParams.withArgs("bin", "url"),
1575 subtarget=subtarget,
1576 prefix=GetVersionPrefix,
1580 "RSYNC_PASSWORD": Interpolate(
1581 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1586 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1590 # delete files which don't exist locally
1594 description="Pruning target files",
1595 descriptionDone="Target files pruned",
1598 "--exclude=/kmods/",
1601 "--ignore-existing",
1603 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1607 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1609 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1611 subtarget=subtarget,
1614 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1615 url=GetRsyncParams.withArgs("bin", "url"),
1617 subtarget=subtarget,
1618 prefix=GetVersionPrefix,
1622 "RSYNC_PASSWORD": Interpolate(
1623 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1629 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1636 description="Uploading kmod archive",
1637 descriptionDone="Kmod archive uploaded",
1642 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1646 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1648 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1650 subtarget=subtarget,
1653 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s/",
1654 url=GetRsyncParams.withArgs("bin", "url"),
1656 subtarget=subtarget,
1657 prefix=GetVersionPrefix,
1661 "RSYNC_PASSWORD": Interpolate(
1662 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1668 doStepIf=IsKmodArchiveAndRsyncEnabled,
1675 description="Finding source archives to upload",
1676 descriptionDone="Source archives to upload found",
1677 command="find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1684 name="sourceupload",
1685 description="Uploading source archives",
1686 descriptionDone="Source archives uploaded",
1689 "--files-from=sourcelist",
1696 "--partial-dir=.~tmp~%(kw:target)s~%(kw:subtarget)s~%(prop:workername)s",
1698 subtarget=subtarget,
1700 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1702 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("src", "url")),
1705 "RSYNC_PASSWORD": Interpolate(
1706 "%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")
1712 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("src", "url")),
1719 description="Reporting disk usage",
1720 command=["df", "-h", "."],
1721 env={"LC_ALL": "C"},
1723 haltOnFailure=False,
1724 flunkOnFailure=False,
1725 warnOnFailure=False,
1733 description="Reporting estimated file space usage",
1734 command=["du", "-sh", "."],
1735 env={"LC_ALL": "C"},
1737 haltOnFailure=False,
1738 flunkOnFailure=False,
1739 warnOnFailure=False,
1747 description="Reporting ccache stats",
1748 command=["ccache", "-s"],
1751 haltOnFailure=False,
1752 flunkOnFailure=False,
1753 warnOnFailure=False,
1754 doStepIf=util.Transform(bool, Property("ccache_command")),
1761 for brname in branchNames:
1762 for target in targets[brname]:
1763 bldrname = brname + "_" + target
1764 c["builders"].append(
1767 workernames=workerNames,
1768 factory=prepareFactory(target),
1772 nextBuild=GetNextBuild,
1773 canStartBuild=canStartBuild,
1778 ####### STATUS TARGETS
1780 # 'status' is a list of Status Targets. The results of each build will be
1781 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1782 # including web pages, email senders, and IRC bots.
1784 if "status_bind" in inip1:
1786 "port": inip1.get("status_bind"),
1787 "plugins": {"waterfall_view": True, "console_view": True, "grid_view": True},
1790 if "status_user" in inip1 and "status_password" in inip1:
1791 c["www"]["auth"] = util.UserPasswordAuth(
1792 [(inip1.get("status_user"), inip1.get("status_password"))]
1794 c["www"]["authz"] = util.Authz(
1795 allowRules=[util.AnyControlEndpointMatcher(role="admins")],
1797 util.RolesFromUsername(
1798 roles=["admins"], usernames=[inip1.get("status_user")]
1804 if ini.has_section("irc"):
1806 irc_host = iniirc.get("host", None)
1807 irc_port = iniirc.getint("port", 6667)
1808 irc_chan = iniirc.get("channel", None)
1809 irc_nick = iniirc.get("nickname", None)
1810 irc_pass = iniirc.get("password", None)
1812 if irc_host and irc_nick and irc_chan:
1813 irc = reporters.IRC(
1818 channels=[irc_chan],
1819 notify_events=["exception", "problem", "recovery"],
1822 c["services"].append(irc)
1824 c["revlink"] = util.RevlinkMatch(
1825 [r"https://git.openwrt.org/openwrt/(.*).git"],
1826 r"https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s",
1832 # This specifies what database buildbot uses to store its state. You can leave
1833 # this at its default for all but the largest installations.
1834 "db_url": "sqlite:///state.sqlite",
1837 c["buildbotNetUsageData"] = None