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 = {}
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 # higher priority (larger positive number) raises position
270 pos = prio + 50 - min(hiprio, 50)
273 # pos order: janitor/local (0), tag builds if any [1..50], !tag builds [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
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") != "main"
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 != "main":
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",
887 "git rev-parse --abbrev-ref HEAD | grep -vxqF HEAD || "
888 "git show-ref --tags --dereference 2>/dev/null | sed -ne "
889 '"/^$(git rev-parse HEAD) / { s|^.*/||; s|\\^.*||; p }" | grep -qE "^v[0-9][0-9]\\."'
898 s='#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
899 workerdest="../ccache_cc.sh",
907 s='#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
908 workerdest="../ccache_cxx.sh",
917 description="Updating feeds",
918 command=["./scripts/feeds", "update"],
919 env=MakeEnv(tryccache=True),
929 description="Installing feeds",
930 command=["./scripts/feeds", "install", "-a"],
931 env=MakeEnv(tryccache=True),
940 s=Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
941 workerdest=".config",
950 descriptionDone=".config seeded",
952 "printf 'CONFIG_TARGET_%(kw:target)s=y\\n"
953 "CONFIG_TARGET_%(kw:target)s_%(kw:subtarget)s=y\\n"
954 "CONFIG_SIGNED_PACKAGES=%(kw:usign:#?|y|n)s\\n' >> .config",
965 description="Populating .config",
966 command=["make", "defconfig"],
971 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
975 description="Checking architecture",
976 descriptionDone="Architecture validated",
977 command='grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config'
978 % (target, subtarget),
983 flunkOnFailure=False, # this is not a build FAILURE - TODO mark build as SKIPPED
989 SetPropertyFromCommand(
992 description="Finding libc suffix",
996 '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }',
1005 name="dlkeybuildpub",
1006 s=Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
1007 workerdest="key-build.pub",
1009 doStepIf=IsUsignEnabled,
1016 s="# fake private key",
1017 workerdest="key-build",
1019 doStepIf=IsUsignEnabled,
1025 name="dlkeybuilducert",
1026 s="# fake certificate",
1027 workerdest="key-build.ucert",
1029 doStepIf=IsUsignEnabled,
1037 description="Preparing dl/",
1038 descriptionDone="dl/ prepared",
1039 command='mkdir -p ../dl && rm -rf "build/dl" && ln -s ../../dl "build/dl"',
1040 workdir=Property("builddir"),
1050 description="Pruning dl/",
1051 descriptionDone="dl/ pruned",
1052 command="find dl/ -mindepth 1 -atime +15 -delete -print",
1054 haltOnFailure=False,
1055 flunkOnFailure=False,
1056 warnOnFailure=False,
1064 description="Building and installing GNU tar",
1065 descriptionDone="GNU tar built and installed",
1068 Interpolate("-j%(prop:nproc:-1)s"),
1069 "tools/tar/compile",
1072 env=MakeEnv(tryccache=True),
1081 description="Populating dl/",
1082 descriptionDone="dl/ populated",
1083 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
1093 description="Cleaning base-files",
1094 command=["make", "package/base-files/clean", "V=s"],
1102 description="Building and installing tools",
1103 descriptionDone="Tools built and installed",
1106 Interpolate("-j%(prop:nproc:-1)s"),
1110 env=MakeEnv(tryccache=True),
1118 description="Building and installing toolchain",
1119 descriptionDone="Toolchain built and installed",
1122 Interpolate("-j%(prop:nproc:-1)s"),
1123 "toolchain/install",
1134 description="Building kmods",
1135 descriptionDone="Kmods built",
1138 Interpolate("-j%(prop:nproc:-1)s"),
1141 "IGNORE_ERRORS=n m",
1149 # find kernel version
1151 SetPropertyFromCommand(
1152 name="kernelversion",
1153 property="kernelversion",
1154 description="Finding the effective Kernel version",
1156 "make --no-print-directory -C target/linux/ "
1157 "val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | "
1158 "xargs printf '%s-%s-%s\\n'"
1160 env={"TOPDIR": Interpolate("%(prop:builddir)s/build")},
1167 description="Cleaning up package build",
1168 descriptionDone="Package build cleaned up",
1169 command=["make", "package/cleanup", "V=s"],
1176 description="Building packages",
1177 descriptionDone="Packages built",
1180 Interpolate("-j%(prop:nproc:-1)s"),
1183 "IGNORE_ERRORS=n m",
1194 description="Installing packages",
1195 descriptionDone="Packages installed",
1198 Interpolate("-j%(prop:nproc:-1)s"),
1210 description="Indexing packages",
1211 descriptionDone="Packages indexed",
1214 Interpolate("-j%(prop:nproc:-1)s"),
1217 "CONFIG_SIGNED_PACKAGES=",
1227 description="Building and installing images",
1228 descriptionDone="Images built and installed",
1231 Interpolate("-j%(prop:nproc:-1)s"),
1243 description="Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
1244 command="make -j1 buildinfo V=s || true",
1252 name="json_overview_image_info",
1253 description="Generating profiles.json in target folder",
1254 command="make -j1 json_overview_image_info V=s || true",
1263 description="Calculating checksums",
1264 descriptionDone="Checksums calculated",
1265 command=["make", "-j1", "checksum", "V=s"],
1274 descriptionDone="Kmod directory created",
1279 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s",
1281 subtarget=subtarget,
1285 doStepIf=IsKmodArchiveEnabled,
1292 description="Preparing kmod archive",
1293 descriptionDone="Kmod archive prepared",
1296 "--include=/kmod-*.ipk",
1300 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/",
1302 subtarget=subtarget,
1305 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1307 subtarget=subtarget,
1311 doStepIf=IsKmodArchiveEnabled,
1318 description="Indexing kmod archive",
1319 descriptionDone="Kmod archive indexed",
1322 Interpolate("-j%(prop:nproc:-1)s"),
1325 "CONFIG_SIGNED_PACKAGES=",
1327 "PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1329 subtarget=subtarget,
1334 doStepIf=IsKmodArchiveEnabled,
1342 descriptionDone="Temporary signing directory prepared",
1343 command=["mkdir", "-p", "%s/signing" % (work_dir)],
1345 doStepIf=IsSignEnabled,
1352 description="Packing files to sign",
1353 descriptionDone="Files to sign packed",
1354 command=Interpolate(
1355 "find bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/ "
1356 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/ "
1357 "-mindepth 1 -maxdepth 2 -type f -name sha256sums -print0 -or "
1358 "-name Packages -print0 | xargs -0 tar -czf sign.tar.gz",
1360 subtarget=subtarget,
1363 doStepIf=IsSignEnabled,
1369 workersrc="sign.tar.gz",
1370 masterdest="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1372 doStepIf=IsSignEnabled,
1379 description="Signing files",
1380 descriptionDone="Files signed",
1382 "%s/signall.sh" % (scripts_dir),
1383 "%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1384 Interpolate("%(prop:branch)s"),
1386 env={"CONFIG_INI": os.getenv("BUILDMASTER_CONFIG", "./config.ini")},
1388 doStepIf=IsSignEnabled,
1395 mastersrc="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1396 workerdest="sign.tar.gz",
1398 doStepIf=IsSignEnabled,
1405 description="Unpacking signed files",
1406 descriptionDone="Signed files unpacked",
1407 command=["tar", "-xzf", "sign.tar.gz"],
1409 doStepIf=IsSignEnabled,
1417 descriptionDone="Upload directory structure prepared",
1422 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s",
1424 subtarget=subtarget,
1425 prefix=GetVersionPrefix,
1435 descriptionDone="Repository symlink prepared",
1441 "../packages-%(kw:basever)s",
1442 basever=util.Transform(GetBaseVersion, Property("branch")),
1445 "tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix
1448 doStepIf=IsNoMasterBuild,
1455 name="kmoddirprepare",
1456 descriptionDone="Kmod archive upload directory prepared",
1461 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s",
1463 subtarget=subtarget,
1464 prefix=GetVersionPrefix,
1468 doStepIf=IsKmodArchiveEnabled,
1475 description="Uploading directory structure",
1476 descriptionDone="Directory structure uploaded",
1477 command=["rsync", Interpolate("-az%(prop:rsync_ipv4:+4)s")]
1481 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url")),
1484 "RSYNC_PASSWORD": Interpolate(
1485 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1491 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1495 # download remote sha256sums to 'target-sha256sums'
1498 name="target-sha256sums",
1499 description="Fetching remote sha256sums for target",
1500 descriptionDone="Remote sha256sums for target fetched",
1501 command=["rsync", Interpolate("-z%(prop:rsync_ipv4:+4)s")]
1505 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/sha256sums",
1506 url=GetRsyncParams.withArgs("bin", "url"),
1508 subtarget=subtarget,
1509 prefix=GetVersionPrefix,
1511 "target-sha256sums",
1514 "RSYNC_PASSWORD": Interpolate(
1515 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1519 haltOnFailure=False,
1520 flunkOnFailure=False,
1521 warnOnFailure=False,
1522 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1526 # build list of files to upload
1529 name="dlsha2rsyncpl",
1530 mastersrc=scripts_dir + "/sha2rsync.pl",
1531 workerdest="../sha2rsync.pl",
1539 description="Building list of files to upload",
1540 descriptionDone="List of files to upload built",
1543 "target-sha256sums",
1545 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1547 subtarget=subtarget,
1558 mastersrc=scripts_dir + "/rsync.sh",
1559 workerdest="../rsync.sh",
1564 # upload new files and update existing ones
1567 name="targetupload",
1568 description="Uploading target files",
1569 descriptionDone="Target files uploaded",
1572 "--exclude=/kmods/",
1573 "--files-from=rsynclist",
1575 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1579 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1581 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1583 subtarget=subtarget,
1586 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1587 url=GetRsyncParams.withArgs("bin", "url"),
1589 subtarget=subtarget,
1590 prefix=GetVersionPrefix,
1594 "RSYNC_PASSWORD": Interpolate(
1595 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1600 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1604 # delete files which don't exist locally
1608 description="Pruning target files",
1609 descriptionDone="Target files pruned",
1612 "--exclude=/kmods/",
1615 "--ignore-existing",
1617 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1621 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1623 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1625 subtarget=subtarget,
1628 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1629 url=GetRsyncParams.withArgs("bin", "url"),
1631 subtarget=subtarget,
1632 prefix=GetVersionPrefix,
1636 "RSYNC_PASSWORD": Interpolate(
1637 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1643 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1650 description="Uploading kmod archive",
1651 descriptionDone="Kmod archive uploaded",
1656 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1660 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1662 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1664 subtarget=subtarget,
1667 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s/",
1668 url=GetRsyncParams.withArgs("bin", "url"),
1670 subtarget=subtarget,
1671 prefix=GetVersionPrefix,
1675 "RSYNC_PASSWORD": Interpolate(
1676 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1682 doStepIf=IsKmodArchiveAndRsyncEnabled,
1689 description="Finding source archives to upload",
1690 descriptionDone="Source archives to upload found",
1692 "find dl/ -maxdepth 1 -type f -not -size 0 "
1693 "-not -name '.*' -not -name '*.hash' -not -name "
1694 "'*.dl' -newer .config -printf '%f\\n' > sourcelist"
1702 name="sourceupload",
1703 description="Uploading source archives",
1704 descriptionDone="Source archives uploaded",
1707 "--files-from=sourcelist",
1714 "--partial-dir=.~tmp~%(kw:target)s~%(kw:subtarget)s~%(prop:workername)s",
1716 subtarget=subtarget,
1718 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1720 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("src", "url")),
1723 "RSYNC_PASSWORD": Interpolate(
1724 "%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")
1730 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("src", "url")),
1737 description="Reporting disk usage",
1738 command=["df", "-h", "."],
1739 env={"LC_ALL": "C"},
1741 haltOnFailure=False,
1742 flunkOnFailure=False,
1743 warnOnFailure=False,
1751 description="Reporting estimated file space usage",
1752 command=["du", "-sh", "."],
1753 env={"LC_ALL": "C"},
1755 haltOnFailure=False,
1756 flunkOnFailure=False,
1757 warnOnFailure=False,
1765 description="Reporting ccache stats",
1766 command=["ccache", "-s"],
1769 haltOnFailure=False,
1770 flunkOnFailure=False,
1771 warnOnFailure=False,
1772 doStepIf=util.Transform(bool, Property("ccache_command")),
1779 for brname in branchNames:
1780 for target in targets[brname]:
1781 bldrname = brname + "_" + target
1782 c["builders"].append(
1785 workernames=workerNames,
1786 factory=prepareFactory(target),
1790 nextBuild=GetNextBuild,
1791 canStartBuild=canStartBuild,
1798 # 'status' is a list of Status Targets. The results of each build will be
1799 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1800 # including web pages, email senders, and IRC bots.
1802 if "status_bind" in inip1:
1804 "port": inip1.get("status_bind"),
1805 "plugins": {"waterfall_view": True, "console_view": True, "grid_view": True},
1808 if "status_user" in inip1 and "status_password" in inip1:
1809 c["www"]["auth"] = util.UserPasswordAuth(
1810 [(inip1.get("status_user"), inip1.get("status_password"))]
1812 c["www"]["authz"] = util.Authz(
1813 allowRules=[util.AnyControlEndpointMatcher(role="admins")],
1815 util.RolesFromUsername(
1816 roles=["admins"], usernames=[inip1.get("status_user")]
1822 if ini.has_section("irc"):
1824 irc_host = iniirc.get("host", None)
1825 irc_port = iniirc.getint("port", 6667)
1826 irc_chan = iniirc.get("channel", None)
1827 irc_nick = iniirc.get("nickname", None)
1828 irc_pass = iniirc.get("password", None)
1830 if irc_host and irc_nick and irc_chan:
1831 irc = reporters.IRC(
1836 channels=[irc_chan],
1837 notify_events=["exception", "problem", "recovery"],
1840 c["services"].append(irc)
1842 c["revlink"] = util.RevlinkMatch(
1843 [r"https://git.openwrt.org/openwrt/(.*).git"],
1844 r"https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s",
1850 # This specifies what database buildbot uses to store its state. You can leave
1851 # this at its default for all but the largest installations.
1852 "db_url": "sqlite:///state.sqlite",
1855 c["buildbotNetUsageData"] = None