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
254 def is_building(bldr):
255 return bool(bldr.building) or bool(bldr.old_building)
258 d = defer.maybeDeferred(getNewestCompleteTimePrio, bldr)
259 d.addCallback(lambda retval: (retval, bldr))
263 ((hiprio, complete_at), bldr) = item
265 # check if we have some high prio build requests pending (i.e. tag builds),
266 # if so, front-run these builders, while preserving the per-branch static priority
268 for name, prio in bldrNamePrio.items():
269 if bldr.name.startswith(name):
270 pos = prio + 50 - min(hiprio, 50) # higher priority (larger positive number) raises position
273 # pos order: janitor/local (0), tag builds per branch order if any [1..50], !tag builds per branch order [51...]
277 complete_at = date.replace(tzinfo=tzutc())
279 if is_building(bldr):
281 complete_at = date.replace(tzinfo=tzutc())
283 return (pos, complete_at, bldr.name)
285 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
286 results.sort(key=bldr_sort)
289 # log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
291 return [r[1] for r in results]
294 c["prioritizeBuilders"] = prioritizeBuilders
296 ####### CHANGESOURCES
302 def populateTargets():
303 """fetch a shallow clone of each configured branch in turn:
304 execute dump-target-info.pl and collate the results to ensure
305 targets that only exist in specific branches get built.
306 This takes a while during master startup but is executed only once.
308 sourcegit = work_dir + "/source.git"
309 for branch in branchNames:
310 log.msg(f"Populating targets for {branch}, this will take time")
312 if os.path.isdir(sourcegit):
313 subprocess.call(["rm", "-rf", sourcegit])
321 "--branch=" + branch,
327 os.makedirs(sourcegit + "/tmp", exist_ok=True)
328 findtargets = subprocess.Popen(
329 ["./scripts/dump-target-info.pl", "targets"],
330 stdout=subprocess.PIPE,
331 stderr=subprocess.DEVNULL,
335 targets[branch] = set()
337 line = findtargets.stdout.readline()
340 ta = line.decode().strip().split(" ")
341 targets[branch].add(ta[0])
343 subprocess.call(["rm", "-rf", sourcegit])
348 # the 'change_source' setting tells the buildmaster how it should find out
349 # about source code changes.
351 c["change_source"] = []
352 c["change_source"].append(
355 workdir=work_dir + "/work.git",
356 branches=branchNames,
364 # Configure the Schedulers, which decide how to react to incoming changes.
367 # Selector for known valid tags
368 class TagChoiceParameter(BaseParameter):
369 spec_attributes = ["strict", "choices"]
373 def __init__(self, name, label=None, **kw):
374 super().__init__(name, label, **kw)
375 self._choice_list = []
377 def getRevTags(self, findtag=None):
381 # we will filter out tags that do no match the configured branches
382 for b in branchNames:
383 basever = re.search(r"-([0-9]+\.[0-9]+)$", b)
385 branchvers.append(basever[1])
387 # grab tags from remote repository
388 alltags = subprocess.Popen(
389 ["git", "ls-remote", "--tags", repo_url], stdout=subprocess.PIPE
393 line = alltags.stdout.readline()
398 (rev, tag) = line.split()
400 # does it match known format? ('vNN.NN.NN(-rcN)')
402 r"\brefs/tags/(v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$",
403 tag.decode().strip(),
406 # only list valid tags matching configured branches
407 if tagver and any(tagver[1][1:].startswith(b) for b in branchvers):
408 # if we want a specific tag, ignore all that don't match
409 if findtag and findtag != tagver[1]:
411 taglist.append({"rev": rev.decode().strip(), "tag": tagver[1]})
417 taglist = [rt["tag"] for rt in self.getRevTags()]
420 key=lambda tag: tag if re.search(r"-rc[0-9]+$", tag) else tag + "-z",
422 taglist.insert(0, "")
424 self._choice_list = taglist
426 return self._choice_list
428 def updateFromKwargs(self, properties, kwargs, **unused):
429 tag = self.getFromKwargs(kwargs)
430 properties[self.name] = tag
432 # find the commit matching the tag
433 findtag = self.getRevTags(tag)
436 raise ValidationError("Couldn't find tag")
438 properties["force_revision"] = findtag[0]["rev"]
440 # find the branch matching the tag
442 branchver = re.search(r"v([0-9]+\.[0-9]+)", tag)
443 for b in branchNames:
444 if b.endswith(branchver[1]):
448 raise ValidationError("Couldn't find branch")
450 properties["force_branch"] = branch
452 def parse_from_arg(self, s):
453 if self.strict and s not in self._choice_list:
454 raise ValidationError(
455 "'%s' does not belong to list of available choices '%s'"
456 % (s, self._choice_list)
462 @defer.inlineCallbacks
463 def builderNames(props):
464 """since we have per branch and per target builders,
465 address the relevant builder for each new buildrequest
466 based on the request's desired branch and target.
468 branch = props.getProperty("branch")
469 target = props.getProperty("target", "")
474 # if that didn't work, try sourcestamp to find a branch
476 # match builders with target branch
477 ss = props.sourcestamps[0]
479 branch = ss["branch"]
481 log.msg("couldn't find builder")
482 return [] # nothing works
484 bname = branch + "_" + target
487 for b in (yield props.master.data.get(("builders",))):
488 if not b["name"].startswith(bname):
490 builders.append(b["name"])
496 c["schedulers"].append(
499 change_filter=util.ChangeFilter(branch=branchNames),
500 treeStableTimer=15 * 60,
501 builderNames=builderNames,
505 c["schedulers"].append(
508 buttonName="Force builds",
509 label="Force build details",
510 builderNames=["00_force_build"],
512 util.CodebaseParameter(
515 branch=util.FixedParameter(name="branch", default=""),
516 revision=util.FixedParameter(name="revision", default=""),
517 repository=util.FixedParameter(name="repository", default=""),
518 project=util.FixedParameter(name="project", default=""),
521 reason=util.StringParameter(
524 default="Trigger build",
529 # NB: avoid nesting to simplify processing of properties
530 util.ChoiceStringParameter(
532 label="Build target",
534 choices=["all"] + [t for b in branchNames for t in targets[b]],
536 TagChoiceParameter(name="tag", label="Build tag", default=""),
541 c["schedulers"].append(
542 schedulers.Triggerable(name="trigger", builderNames=builderNames, priority=20)
547 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
548 # what steps, and which workers can execute them. Note that any particular build will
549 # only take place on one worker.
552 def IsNoMasterBuild(step):
553 return step.getProperty("branch") != "master"
556 def IsUsignEnabled(step):
557 branch = step.getProperty("branch")
558 return branch and branches[branch].get("usign_key")
561 def IsSignEnabled(step):
562 branch = step.getProperty("branch")
563 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
566 def IsKmodArchiveEnabled(step):
567 branch = step.getProperty("branch")
568 return branch and branches[branch].get("kmod_archive")
571 def IsKmodArchiveAndRsyncEnabled(step):
572 branch = step.getProperty("branch")
573 return bool(IsKmodArchiveEnabled(step) and branches[branch].get("bin_url"))
576 def GetBaseVersion(branch):
577 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
578 return branch.split("-")[1]
584 def GetVersionPrefix(props):
585 branch = props.getProperty("branch")
586 basever = GetBaseVersion(branch)
587 if props.hasProperty("tag") and re.match(
588 r"^v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]
590 return "%s/" % props["tag"][1:]
591 elif basever != "master":
592 return "%s-SNAPSHOT/" % basever
598 def GetConfigSeed(props):
599 branch = props.getProperty("branch")
600 return branch and branches[branch].get("config_seed") or ""
604 def GetRsyncParams(props, srcorbin, urlorkey):
605 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
606 branch = props.getProperty("branch")
607 opt = srcorbin + "_" + urlorkey
608 return branch and branches[branch].get(opt)
612 def GetUsignKey(props):
613 branch = props.getProperty("branch")
614 return branch and branches[branch].get("usign_key")
617 def GetNextBuild(builder, requests):
620 # order tagged build first
621 if r.properties.hasProperty("tag"):
625 # log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
629 def MakeEnv(overrides=None, tryccache=False):
631 "CCC": Interpolate("%(prop:cc_command:-gcc)s"),
632 "CCXX": Interpolate("%(prop:cxx_command:-g++)s"),
635 env["CC"] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
636 env["CXX"] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
637 env["CCACHE"] = Interpolate("%(prop:ccache_command:-)s")
639 env["CC"] = env["CCC"]
640 env["CXX"] = env["CCXX"]
642 if overrides is not None:
643 env.update(overrides)
648 def NetLockDl(props, extralock=None):
650 if props.hasProperty("dl_lock"):
651 lock = NetLocks[props["dl_lock"]]
653 return [lock.access("exclusive")]
659 def NetLockUl(props):
661 if props.hasProperty("ul_lock"):
662 lock = NetLocks[props["ul_lock"]]
664 return [lock.access("exclusive")]
669 def IsTargetSelected(target):
670 def CheckTargetProperty(step):
671 selected_target = step.getProperty("target", "all")
672 if selected_target != "all" and selected_target != target:
676 return CheckTargetProperty
680 def UsignSec2Pub(props):
681 branch = props.getProperty("branch")
684 branches[branch].get("usign_comment") or "untrusted comment: secret key"
686 seckey = branches[branch].get("usign_key")
687 seckey = base64.b64decode(seckey)
691 return "{}\n{}".format(
692 re.sub(r"\bsecret key$", "public key", comment),
693 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]),
697 def canStartBuild(builder, wfb, request):
698 """filter out non tag requests for tag_only workers."""
699 wtagonly = wfb.worker.properties.getProperty("tag_only")
700 tag = request.properties.getProperty("tag")
702 if wtagonly and not tag:
712 for worker in c["workers"]:
713 workerNames.append(worker.workername)
715 # add a single LocalWorker to handle the forcebuild builder
716 c["workers"].append(LocalWorker("__local_force_build", max_builds=1))
718 force_factory = BuildFactory()
719 force_factory.addStep(
721 name="trigger_build",
722 schedulerNames=["trigger"],
726 "branch": Property("force_branch"),
727 "revision": Property("force_revision"),
728 "repository": repo_url,
733 "reason": Property("reason"),
734 "tag": Property("tag"),
735 "target": Property("target"),
740 c["builders"].append(
742 name="00_force_build", workername="__local_force_build", factory=force_factory
747 # NB the phase1 build factory assumes workers are single-build only
748 def prepareFactory(target):
749 (target, subtarget) = target.split("/")
751 factory = BuildFactory()
753 # setup shared work directory if required
757 descriptionDone="Shared work directory set up",
758 command='test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
764 # find number of cores
766 SetPropertyFromCommand(
769 description="Finding number of CPUs",
774 # find gcc and g++ compilers
778 mastersrc=scripts_dir + "/findbin.pl",
779 workerdest="../findbin.pl",
785 SetPropertyFromCommand(
787 property="cc_command",
788 description="Finding gcc command",
789 command=["../findbin.pl", "gcc", "", ""],
795 SetPropertyFromCommand(
797 property="cxx_command",
798 description="Finding g++ command",
799 command=["../findbin.pl", "g++", "", ""],
804 # see if ccache is available
806 SetPropertyFromCommand(
808 property="ccache_command",
809 description="Testing for ccache command",
810 command=["which", "ccache"],
812 flunkOnFailure=False,
814 hideStepIf=lambda r, s: r == results.FAILURE,
818 # check out the source
820 # if repo doesn't exist: 'git clone repourl'
821 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -f -f -d -x'. Only works with mode='full'
822 # git cat-file -e <commit>
823 # git checkout -f <commit>
824 # git checkout -B <branch>
837 # workaround for https://github.com/openwrt/buildbot/issues/5
840 name="git me once more please",
853 description="Fetching Git remote refs",
854 descriptionDone="Git remote refs fetched",
860 "+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s"
867 # getver.sh requires local branches to track upstream otherwise version computation fails.
868 # Git() does not set tracking branches when cloning or switching, so work around this here
871 name="trackupstream",
872 description="Setting upstream branch",
873 descriptionDone="getver.sh is happy now",
874 command=["git", "branch", "-u", Interpolate("origin/%(prop:branch)s")],
879 # Verify that Git HEAD points to a tag or branch
880 # Ref: https://web.archive.org/web/20190729224316/http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
884 description="Ensuring that Git HEAD is pointing to a branch or tag",
885 descriptionDone="Git HEAD is sane",
886 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]\\."',
894 s='#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
895 workerdest="../ccache_cc.sh",
903 s='#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
904 workerdest="../ccache_cxx.sh",
913 description="Updating feeds",
914 command=["./scripts/feeds", "update"],
915 env=MakeEnv(tryccache=True),
925 description="Installing feeds",
926 command=["./scripts/feeds", "install", "-a"],
927 env=MakeEnv(tryccache=True),
936 s=Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
937 workerdest=".config",
946 descriptionDone=".config seeded",
948 "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",
959 description="Populating .config",
960 command=["make", "defconfig"],
965 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
969 description="Checking architecture",
970 descriptionDone="Architecture validated",
971 command='grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config'
972 % (target, subtarget),
977 flunkOnFailure=False, # this is not a build FAILURE - TODO mark build as SKIPPED
983 SetPropertyFromCommand(
986 description="Finding libc suffix",
990 '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }',
999 name="dlkeybuildpub",
1000 s=Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
1001 workerdest="key-build.pub",
1003 doStepIf=IsUsignEnabled,
1010 s="# fake private key",
1011 workerdest="key-build",
1013 doStepIf=IsUsignEnabled,
1019 name="dlkeybuilducert",
1020 s="# fake certificate",
1021 workerdest="key-build.ucert",
1023 doStepIf=IsUsignEnabled,
1031 description="Preparing dl/",
1032 descriptionDone="dl/ prepared",
1033 command='mkdir -p ../dl && rm -rf "build/dl" && ln -s ../../dl "build/dl"',
1034 workdir=Property("builddir"),
1044 description="Pruning dl/",
1045 descriptionDone="dl/ pruned",
1046 command="find dl/ -mindepth 1 -atime +15 -delete -print",
1048 haltOnFailure=False,
1049 flunkOnFailure=False,
1050 warnOnFailure=False,
1058 description="Building and installing GNU tar",
1059 descriptionDone="GNU tar built and installed",
1062 Interpolate("-j%(prop:nproc:-1)s"),
1063 "tools/tar/compile",
1066 env=MakeEnv(tryccache=True),
1075 description="Populating dl/",
1076 descriptionDone="dl/ populated",
1077 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
1087 description="Cleaning base-files",
1088 command=["make", "package/base-files/clean", "V=s"],
1096 description="Building and installing tools",
1097 descriptionDone="Tools built and installed",
1100 Interpolate("-j%(prop:nproc:-1)s"),
1104 env=MakeEnv(tryccache=True),
1112 description="Building and installing toolchain",
1113 descriptionDone="Toolchain built and installed",
1116 Interpolate("-j%(prop:nproc:-1)s"),
1117 "toolchain/install",
1128 description="Building kmods",
1129 descriptionDone="Kmods built",
1132 Interpolate("-j%(prop:nproc:-1)s"),
1135 "IGNORE_ERRORS=n m",
1143 # find kernel version
1145 SetPropertyFromCommand(
1146 name="kernelversion",
1147 property="kernelversion",
1148 description="Finding the effective Kernel version",
1149 command="make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
1150 env={"TOPDIR": Interpolate("%(prop:builddir)s/build")},
1157 description="Cleaning up package build",
1158 descriptionDone="Package build cleaned up",
1159 command=["make", "package/cleanup", "V=s"],
1166 description="Building packages",
1167 descriptionDone="Packages built",
1170 Interpolate("-j%(prop:nproc:-1)s"),
1173 "IGNORE_ERRORS=n m",
1184 description="Installing packages",
1185 descriptionDone="Packages installed",
1188 Interpolate("-j%(prop:nproc:-1)s"),
1200 description="Indexing packages",
1201 descriptionDone="Packages indexed",
1204 Interpolate("-j%(prop:nproc:-1)s"),
1207 "CONFIG_SIGNED_PACKAGES=",
1217 description="Building and installing images",
1218 descriptionDone="Images built and installed",
1221 Interpolate("-j%(prop:nproc:-1)s"),
1233 description="Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
1234 command="make -j1 buildinfo V=s || true",
1242 name="json_overview_image_info",
1243 description="Generating profiles.json in target folder",
1244 command="make -j1 json_overview_image_info V=s || true",
1253 description="Calculating checksums",
1254 descriptionDone="Checksums calculated",
1255 command=["make", "-j1", "checksum", "V=s"],
1264 descriptionDone="Kmod directory created",
1269 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s",
1271 subtarget=subtarget,
1275 doStepIf=IsKmodArchiveEnabled,
1282 description="Preparing kmod archive",
1283 descriptionDone="Kmod archive prepared",
1286 "--include=/kmod-*.ipk",
1290 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/",
1292 subtarget=subtarget,
1295 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1297 subtarget=subtarget,
1301 doStepIf=IsKmodArchiveEnabled,
1308 description="Indexing kmod archive",
1309 descriptionDone="Kmod archive indexed",
1312 Interpolate("-j%(prop:nproc:-1)s"),
1315 "CONFIG_SIGNED_PACKAGES=",
1317 "PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1319 subtarget=subtarget,
1324 doStepIf=IsKmodArchiveEnabled,
1332 descriptionDone="Temporary signing directory prepared",
1333 command=["mkdir", "-p", "%s/signing" % (work_dir)],
1335 doStepIf=IsSignEnabled,
1342 description="Packing files to sign",
1343 descriptionDone="Files to sign packed",
1344 command=Interpolate(
1345 "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",
1347 subtarget=subtarget,
1350 doStepIf=IsSignEnabled,
1356 workersrc="sign.tar.gz",
1357 masterdest="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1359 doStepIf=IsSignEnabled,
1366 description="Signing files",
1367 descriptionDone="Files signed",
1369 "%s/signall.sh" % (scripts_dir),
1370 "%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1371 Interpolate("%(prop:branch)s"),
1373 env={"CONFIG_INI": os.getenv("BUILDMASTER_CONFIG", "./config.ini")},
1375 doStepIf=IsSignEnabled,
1382 mastersrc="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1383 workerdest="sign.tar.gz",
1385 doStepIf=IsSignEnabled,
1392 description="Unpacking signed files",
1393 descriptionDone="Signed files unpacked",
1394 command=["tar", "-xzf", "sign.tar.gz"],
1396 doStepIf=IsSignEnabled,
1404 descriptionDone="Upload directory structure prepared",
1409 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s",
1411 subtarget=subtarget,
1412 prefix=GetVersionPrefix,
1422 descriptionDone="Repository symlink prepared",
1428 "../packages-%(kw:basever)s",
1429 basever=util.Transform(GetBaseVersion, Property("branch")),
1432 "tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix
1435 doStepIf=IsNoMasterBuild,
1442 name="kmoddirprepare",
1443 descriptionDone="Kmod archive upload directory prepared",
1448 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s",
1450 subtarget=subtarget,
1451 prefix=GetVersionPrefix,
1455 doStepIf=IsKmodArchiveEnabled,
1462 description="Uploading directory structure",
1463 descriptionDone="Directory structure uploaded",
1464 command=["rsync", Interpolate("-az%(prop:rsync_ipv4:+4)s")]
1468 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url")),
1471 "RSYNC_PASSWORD": Interpolate(
1472 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1478 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1482 # download remote sha256sums to 'target-sha256sums'
1485 name="target-sha256sums",
1486 description="Fetching remote sha256sums for target",
1487 descriptionDone="Remote sha256sums for target fetched",
1488 command=["rsync", Interpolate("-z%(prop:rsync_ipv4:+4)s")]
1492 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/sha256sums",
1493 url=GetRsyncParams.withArgs("bin", "url"),
1495 subtarget=subtarget,
1496 prefix=GetVersionPrefix,
1498 "target-sha256sums",
1501 "RSYNC_PASSWORD": Interpolate(
1502 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1506 haltOnFailure=False,
1507 flunkOnFailure=False,
1508 warnOnFailure=False,
1509 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1513 # build list of files to upload
1516 name="dlsha2rsyncpl",
1517 mastersrc=scripts_dir + "/sha2rsync.pl",
1518 workerdest="../sha2rsync.pl",
1526 description="Building list of files to upload",
1527 descriptionDone="List of files to upload built",
1530 "target-sha256sums",
1532 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1534 subtarget=subtarget,
1545 mastersrc=scripts_dir + "/rsync.sh",
1546 workerdest="../rsync.sh",
1551 # upload new files and update existing ones
1554 name="targetupload",
1555 description="Uploading target files",
1556 descriptionDone="Target files uploaded",
1559 "--exclude=/kmods/",
1560 "--files-from=rsynclist",
1562 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1566 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1568 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1570 subtarget=subtarget,
1573 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1574 url=GetRsyncParams.withArgs("bin", "url"),
1576 subtarget=subtarget,
1577 prefix=GetVersionPrefix,
1581 "RSYNC_PASSWORD": Interpolate(
1582 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1587 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1591 # delete files which don't exist locally
1595 description="Pruning target files",
1596 descriptionDone="Target files pruned",
1599 "--exclude=/kmods/",
1602 "--ignore-existing",
1604 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1608 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1610 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1612 subtarget=subtarget,
1615 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1616 url=GetRsyncParams.withArgs("bin", "url"),
1618 subtarget=subtarget,
1619 prefix=GetVersionPrefix,
1623 "RSYNC_PASSWORD": Interpolate(
1624 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1630 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1637 description="Uploading kmod archive",
1638 descriptionDone="Kmod archive uploaded",
1643 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1647 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1649 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1651 subtarget=subtarget,
1654 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s/",
1655 url=GetRsyncParams.withArgs("bin", "url"),
1657 subtarget=subtarget,
1658 prefix=GetVersionPrefix,
1662 "RSYNC_PASSWORD": Interpolate(
1663 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1669 doStepIf=IsKmodArchiveAndRsyncEnabled,
1676 description="Finding source archives to upload",
1677 descriptionDone="Source archives to upload found",
1678 command="find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1685 name="sourceupload",
1686 description="Uploading source archives",
1687 descriptionDone="Source archives uploaded",
1690 "--files-from=sourcelist",
1697 "--partial-dir=.~tmp~%(kw:target)s~%(kw:subtarget)s~%(prop:workername)s",
1699 subtarget=subtarget,
1701 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1703 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("src", "url")),
1706 "RSYNC_PASSWORD": Interpolate(
1707 "%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")
1713 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("src", "url")),
1720 description="Reporting disk usage",
1721 command=["df", "-h", "."],
1722 env={"LC_ALL": "C"},
1724 haltOnFailure=False,
1725 flunkOnFailure=False,
1726 warnOnFailure=False,
1734 description="Reporting estimated file space usage",
1735 command=["du", "-sh", "."],
1736 env={"LC_ALL": "C"},
1738 haltOnFailure=False,
1739 flunkOnFailure=False,
1740 warnOnFailure=False,
1748 description="Reporting ccache stats",
1749 command=["ccache", "-s"],
1752 haltOnFailure=False,
1753 flunkOnFailure=False,
1754 warnOnFailure=False,
1755 doStepIf=util.Transform(bool, Property("ccache_command")),
1762 for brname in branchNames:
1763 for target in targets[brname]:
1764 bldrname = brname + "_" + target
1765 c["builders"].append(
1768 workernames=workerNames,
1769 factory=prepareFactory(target),
1773 nextBuild=GetNextBuild,
1774 canStartBuild=canStartBuild,
1779 ####### STATUS TARGETS
1781 # 'status' is a list of Status Targets. The results of each build will be
1782 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1783 # including web pages, email senders, and IRC bots.
1785 if "status_bind" in inip1:
1787 "port": inip1.get("status_bind"),
1788 "plugins": {"waterfall_view": True, "console_view": True, "grid_view": True},
1791 if "status_user" in inip1 and "status_password" in inip1:
1792 c["www"]["auth"] = util.UserPasswordAuth(
1793 [(inip1.get("status_user"), inip1.get("status_password"))]
1795 c["www"]["authz"] = util.Authz(
1796 allowRules=[util.AnyControlEndpointMatcher(role="admins")],
1798 util.RolesFromUsername(
1799 roles=["admins"], usernames=[inip1.get("status_user")]
1805 if ini.has_section("irc"):
1807 irc_host = iniirc.get("host", None)
1808 irc_port = iniirc.getint("port", 6667)
1809 irc_chan = iniirc.get("channel", None)
1810 irc_nick = iniirc.get("nickname", None)
1811 irc_pass = iniirc.get("password", None)
1813 if irc_host and irc_nick and irc_chan:
1814 irc = reporters.IRC(
1819 channels=[irc_chan],
1820 notify_events=["exception", "problem", "recovery"],
1823 c["services"].append(irc)
1825 c["revlink"] = util.RevlinkMatch(
1826 [r"https://git.openwrt.org/openwrt/(.*).git"],
1827 r"https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s",
1833 # This specifies what database buildbot uses to store its state. You can leave
1834 # this at its default for all but the largest installations.
1835 "db_url": "sqlite:///state.sqlite",
1838 c["buildbotNetUsageData"] = None