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 getNewestCompleteTime(bldr):
195 """Returns 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: datetime instance or None, via Deferred
204 bldrid = yield bldr.getBuilderId()
205 completed = yield bldr.master.data.get(
206 ("builders", bldrid, "buildrequests"),
208 resultspec.Filter("complete", "eq", [True]),
209 resultspec.Filter("results", "ne", [results.SKIPPED]),
211 order=["-complete_at"],
217 complete_at = completed[0]["complete_at"]
219 last_build = yield bldr.master.data.get(
222 resultspec.Filter("builderid", "eq", [bldrid]),
224 order=["-started_at"],
228 if last_build and last_build[0]:
229 last_complete_at = last_build[0]["complete_at"]
230 if last_complete_at and (last_complete_at > complete_at):
231 return last_complete_at
236 @defer.inlineCallbacks
237 def prioritizeBuilders(master, builders):
238 """Returns sorted list of builders by their last timestamp of completed and
239 not skipped build, ordered first by branch name.
241 @returns: list of sorted builders
244 bldrNamePrio = {"__Janitor": 0, "00_force_build": 0}
246 for bname in branchNames:
247 bldrNamePrio[bname] = i
250 def is_building(bldr):
251 return bool(bldr.building) or bool(bldr.old_building)
254 d = defer.maybeDeferred(getNewestCompleteTime, bldr)
255 d.addCallback(lambda complete_at: (complete_at, bldr))
259 (complete_at, bldr) = item
262 for name, prio in bldrNamePrio.items():
263 if bldr.name.startswith(name):
269 complete_at = date.replace(tzinfo=tzutc())
271 if is_building(bldr):
273 complete_at = date.replace(tzinfo=tzutc())
275 return (pos, complete_at, bldr.name)
277 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
278 results.sort(key=bldr_sort)
281 # log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
283 return [r[1] for r in results]
286 c["prioritizeBuilders"] = prioritizeBuilders
288 ####### CHANGESOURCES
294 def populateTargets():
295 """fetch a shallow clone of each configured branch in turn:
296 execute dump-target-info.pl and collate the results to ensure
297 targets that only exist in specific branches get built.
298 This takes a while during master startup but is executed only once.
300 log.msg("Populating targets, this will take time")
301 sourcegit = work_dir + "/source.git"
302 for branch in branchNames:
303 if os.path.isdir(sourcegit):
304 subprocess.call(["rm", "-rf", sourcegit])
312 "--branch=" + branch,
318 os.makedirs(sourcegit + "/tmp", exist_ok=True)
319 findtargets = subprocess.Popen(
320 ["./scripts/dump-target-info.pl", "targets"],
321 stdout=subprocess.PIPE,
322 stderr=subprocess.DEVNULL,
327 line = findtargets.stdout.readline()
330 ta = line.decode().strip().split(" ")
333 subprocess.call(["rm", "-rf", sourcegit])
338 # the 'change_source' setting tells the buildmaster how it should find out
339 # about source code changes.
341 c["change_source"] = []
342 c["change_source"].append(
345 workdir=work_dir + "/work.git",
346 branches=branchNames,
354 # Configure the Schedulers, which decide how to react to incoming changes.
357 # Selector for known valid tags
358 class TagChoiceParameter(BaseParameter):
359 spec_attributes = ["strict", "choices"]
363 def __init__(self, name, label=None, **kw):
364 super().__init__(name, label, **kw)
365 self._choice_list = []
367 def getRevTags(self, findtag=None):
371 # we will filter out tags that do no match the configured branches
372 for b in branchNames:
373 basever = re.search(r"-([0-9]+\.[0-9]+)$", b)
375 branchvers.append(basever[1])
377 # grab tags from remote repository
378 alltags = subprocess.Popen(
379 ["git", "ls-remote", "--tags", repo_url], stdout=subprocess.PIPE
383 line = alltags.stdout.readline()
388 (rev, tag) = line.split()
390 # does it match known format? ('vNN.NN.NN(-rcN)')
392 r"\brefs/tags/(v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$",
393 tag.decode().strip(),
396 # only list valid tags matching configured branches
397 if tagver and any(tagver[1][1:].startswith(b) for b in branchvers):
398 # if we want a specific tag, ignore all that don't match
399 if findtag and findtag != tagver[1]:
401 taglist.append({"rev": rev.decode().strip(), "tag": tagver[1]})
407 taglist = [rt["tag"] for rt in self.getRevTags()]
410 key=lambda tag: tag if re.search(r"-rc[0-9]+$", tag) else tag + "-z",
412 taglist.insert(0, "")
414 self._choice_list = taglist
416 return self._choice_list
418 def updateFromKwargs(self, properties, kwargs, **unused):
419 tag = self.getFromKwargs(kwargs)
420 properties[self.name] = tag
422 # find the commit matching the tag
423 findtag = self.getRevTags(tag)
426 raise ValidationError("Couldn't find tag")
428 properties["force_revision"] = findtag[0]["rev"]
430 # find the branch matching the tag
432 branchver = re.search(r"v([0-9]+\.[0-9]+)", tag)
433 for b in branchNames:
434 if b.endswith(branchver[1]):
438 raise ValidationError("Couldn't find branch")
440 properties["force_branch"] = branch
442 def parse_from_arg(self, s):
443 if self.strict and s not in self._choice_list:
444 raise ValidationError(
445 "'%s' does not belong to list of available choices '%s'"
446 % (s, self._choice_list)
452 @defer.inlineCallbacks
453 def builderNames(props):
454 """since we have per branch and per target builders,
455 address the relevant builder for each new buildrequest
456 based on the request's desired branch and target.
458 branch = props.getProperty("branch")
459 target = props.getProperty("target", "")
464 # if that didn't work, try sourcestamp to find a branch
466 # match builders with target branch
467 ss = props.sourcestamps[0]
469 branch = ss["branch"]
471 log.msg("couldn't find builder")
472 return [] # nothing works
474 bname = branch + "_" + target
477 for b in (yield props.master.data.get(("builders",))):
478 if not b["name"].startswith(bname):
480 builders.append(b["name"])
486 c["schedulers"].append(
489 change_filter=util.ChangeFilter(branch=branchNames),
490 treeStableTimer=15 * 60,
491 builderNames=builderNames,
495 c["schedulers"].append(
498 buttonName="Force builds",
499 label="Force build details",
500 builderNames=["00_force_build"],
502 util.CodebaseParameter(
505 branch=util.FixedParameter(name="branch", default=""),
506 revision=util.FixedParameter(name="revision", default=""),
507 repository=util.FixedParameter(name="repository", default=""),
508 project=util.FixedParameter(name="project", default=""),
511 reason=util.StringParameter(
514 default="Trigger build",
519 # NB: avoid nesting to simplify processing of properties
520 util.ChoiceStringParameter(
522 label="Build target",
524 choices=["all"] + list(targets),
526 TagChoiceParameter(name="tag", label="Build tag", default=""),
531 c["schedulers"].append(
532 schedulers.Triggerable(name="trigger", builderNames=builderNames)
537 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
538 # what steps, and which workers can execute them. Note that any particular build will
539 # only take place on one worker.
542 def IsNoMasterBuild(step):
543 return step.getProperty("branch") != "master"
546 def IsUsignEnabled(step):
547 branch = step.getProperty("branch")
548 return branch and branches[branch].get("usign_key")
551 def IsSignEnabled(step):
552 branch = step.getProperty("branch")
553 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
556 def IsKmodArchiveEnabled(step):
557 branch = step.getProperty("branch")
558 return branch and branches[branch].get("kmod_archive")
561 def IsKmodArchiveAndRsyncEnabled(step):
562 branch = step.getProperty("branch")
563 return bool(IsKmodArchiveEnabled(step) and branches[branch].get("bin_url"))
566 def GetBaseVersion(branch):
567 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
568 return branch.split("-")[1]
574 def GetVersionPrefix(props):
575 branch = props.getProperty("branch")
576 basever = GetBaseVersion(branch)
577 if props.hasProperty("tag") and re.match(
578 r"^v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]
580 return "%s/" % props["tag"][1:]
581 elif basever != "master":
582 return "%s-SNAPSHOT/" % basever
588 def GetConfigSeed(props):
589 branch = props.getProperty("branch")
590 return branch and branches[branch].get("config_seed") or ""
594 def GetRsyncParams(props, srcorbin, urlorkey):
595 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
596 branch = props.getProperty("branch")
597 opt = srcorbin + "_" + urlorkey
598 return branch and branches[branch].get(opt)
602 def GetUsignKey(props):
603 branch = props.getProperty("branch")
604 return branch and branches[branch].get("usign_key")
607 def GetNextBuild(builder, requests):
610 # order tagged build first
611 if r.properties.hasProperty("tag"):
615 # log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
619 def MakeEnv(overrides=None, tryccache=False):
621 "CCC": Interpolate("%(prop:cc_command:-gcc)s"),
622 "CCXX": Interpolate("%(prop:cxx_command:-g++)s"),
625 env["CC"] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
626 env["CXX"] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
627 env["CCACHE"] = Interpolate("%(prop:ccache_command:-)s")
629 env["CC"] = env["CCC"]
630 env["CXX"] = env["CCXX"]
632 if overrides is not None:
633 env.update(overrides)
638 def NetLockDl(props, extralock=None):
640 if props.hasProperty("dl_lock"):
641 lock = NetLocks[props["dl_lock"]]
643 return [lock.access("exclusive")]
649 def NetLockUl(props):
651 if props.hasProperty("ul_lock"):
652 lock = NetLocks[props["ul_lock"]]
654 return [lock.access("exclusive")]
659 def IsTargetSelected(target):
660 def CheckTargetProperty(step):
661 selected_target = step.getProperty("target", "all")
662 if selected_target != "all" and selected_target != target:
666 return CheckTargetProperty
670 def UsignSec2Pub(props):
671 branch = props.getProperty("branch")
674 branches[branch].get("usign_comment") or "untrusted comment: secret key"
676 seckey = branches[branch].get("usign_key")
677 seckey = base64.b64decode(seckey)
681 return "{}\n{}".format(
682 re.sub(r"\bsecret key$", "public key", comment),
683 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]),
687 def canStartBuild(builder, wfb, request):
688 """filter out non tag requests for tag_only workers."""
689 wtagonly = wfb.worker.properties.getProperty("tag_only")
690 tag = request.properties.getProperty("tag")
692 if wtagonly and not tag:
702 for worker in c["workers"]:
703 workerNames.append(worker.workername)
705 # add a single LocalWorker to handle the forcebuild builder
706 c["workers"].append(LocalWorker("__local_force_build", max_builds=1))
708 force_factory = BuildFactory()
709 force_factory.addStep(
711 name="trigger_build",
712 schedulerNames=["trigger"],
716 "branch": Property("force_branch"),
717 "revision": Property("force_revision"),
718 "repository": repo_url,
723 "reason": Property("reason"),
724 "tag": Property("tag"),
725 "target": Property("target"),
730 c["builders"].append(
732 name="00_force_build", workername="__local_force_build", factory=force_factory
737 # NB the phase1 build factory assumes workers are single-build only
738 for target in targets:
739 ts = target.split("/")
741 factory = BuildFactory()
743 # setup shared work directory if required
747 descriptionDone="Shared work directory set up",
748 command='test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
754 # find number of cores
756 SetPropertyFromCommand(
759 description="Finding number of CPUs",
764 # find gcc and g++ compilers
768 mastersrc=scripts_dir + "/findbin.pl",
769 workerdest="../findbin.pl",
775 SetPropertyFromCommand(
777 property="cc_command",
778 description="Finding gcc command",
779 command=["../findbin.pl", "gcc", "", ""],
785 SetPropertyFromCommand(
787 property="cxx_command",
788 description="Finding g++ command",
789 command=["../findbin.pl", "g++", "", ""],
794 # see if ccache is available
796 SetPropertyFromCommand(
798 property="ccache_command",
799 description="Testing for ccache command",
800 command=["which", "ccache"],
802 flunkOnFailure=False,
804 hideStepIf=lambda r, s: r == results.FAILURE,
808 # check out the source
810 # if repo doesn't exist: 'git clone repourl'
811 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -f -f -d -x'. Only works with mode='full'
812 # git cat-file -e <commit>
813 # git checkout -f <commit>
814 # git checkout -B <branch>
827 # workaround for https://github.com/openwrt/buildbot/issues/5
830 name="git me once more please",
843 description="Fetching Git remote refs",
844 descriptionDone="Git remote refs fetched",
850 "+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s"
857 # getver.sh requires local branches to track upstream otherwise version computation fails.
858 # Git() does not set tracking branches when cloning or switching, so work around this here
861 name="trackupstream",
862 description="Setting upstream branch",
863 descriptionDone="getver.sh is happy now",
864 command=["git", "branch", "-u", Interpolate("origin/%(prop:branch)s")],
869 # Verify that Git HEAD points to a tag or branch
870 # Ref: https://web.archive.org/web/20190729224316/http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
874 description="Ensuring that Git HEAD is pointing to a branch or tag",
875 descriptionDone="Git HEAD is sane",
876 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]\\."',
884 s='#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
885 workerdest="../ccache_cc.sh",
893 s='#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
894 workerdest="../ccache_cxx.sh",
903 description="Updating feeds",
904 command=["./scripts/feeds", "update"],
905 env=MakeEnv(tryccache=True),
915 description="Installing feeds",
916 command=["./scripts/feeds", "install", "-a"],
917 env=MakeEnv(tryccache=True),
926 s=Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
927 workerdest=".config",
936 descriptionDone=".config seeded",
938 "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",
949 description="Populating .config",
950 command=["make", "defconfig"],
955 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
959 description="Checking architecture",
960 descriptionDone="Architecture validated",
961 command='grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config'
967 flunkOnFailure=False, # this is not a build FAILURE - TODO mark build as SKIPPED
973 SetPropertyFromCommand(
976 description="Finding libc suffix",
980 '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }',
989 name="dlkeybuildpub",
990 s=Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
991 workerdest="key-build.pub",
993 doStepIf=IsUsignEnabled,
1000 s="# fake private key",
1001 workerdest="key-build",
1003 doStepIf=IsUsignEnabled,
1009 name="dlkeybuilducert",
1010 s="# fake certificate",
1011 workerdest="key-build.ucert",
1013 doStepIf=IsUsignEnabled,
1021 description="Preparing dl/",
1022 descriptionDone="dl/ prepared",
1023 command='mkdir -p ../dl && rm -rf "build/dl" && ln -s ../../dl "build/dl"',
1024 workdir=Property("builddir"),
1034 description="Pruning dl/",
1035 descriptionDone="dl/ pruned",
1036 command="find dl/ -mindepth 1 -atime +15 -delete -print",
1045 description="Building and installing GNU tar",
1046 descriptionDone="GNU tar built and installed",
1049 Interpolate("-j%(prop:nproc:-1)s"),
1050 "tools/tar/compile",
1053 env=MakeEnv(tryccache=True),
1062 description="Populating dl/",
1063 descriptionDone="dl/ populated",
1064 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
1074 description="Cleaning base-files",
1075 command=["make", "package/base-files/clean", "V=s"],
1083 description="Building and installing tools",
1084 descriptionDone="Tools built and installed",
1087 Interpolate("-j%(prop:nproc:-1)s"),
1091 env=MakeEnv(tryccache=True),
1099 description="Building and installing toolchain",
1100 descriptionDone="Toolchain built and installed",
1103 Interpolate("-j%(prop:nproc:-1)s"),
1104 "toolchain/install",
1115 description="Building kmods",
1116 descriptionDone="Kmods built",
1119 Interpolate("-j%(prop:nproc:-1)s"),
1122 "IGNORE_ERRORS=n m",
1130 # find kernel version
1132 SetPropertyFromCommand(
1133 name="kernelversion",
1134 property="kernelversion",
1135 description="Finding the effective Kernel version",
1136 command="make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
1137 env={"TOPDIR": Interpolate("%(prop:builddir)s/build")},
1144 description="Cleaning up package build",
1145 descriptionDone="Package build cleaned up",
1146 command=["make", "package/cleanup", "V=s"],
1153 description="Building packages",
1154 descriptionDone="Packages built",
1157 Interpolate("-j%(prop:nproc:-1)s"),
1160 "IGNORE_ERRORS=n m",
1171 description="Installing packages",
1172 descriptionDone="Packages installed",
1175 Interpolate("-j%(prop:nproc:-1)s"),
1187 description="Indexing packages",
1188 descriptionDone="Packages indexed",
1191 Interpolate("-j%(prop:nproc:-1)s"),
1194 "CONFIG_SIGNED_PACKAGES=",
1204 description="Building and installing images",
1205 descriptionDone="Images built and installed",
1208 Interpolate("-j%(prop:nproc:-1)s"),
1220 description="Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
1221 command="make -j1 buildinfo V=s || true",
1229 name="json_overview_image_info",
1230 description="Generating profiles.json in target folder",
1231 command="make -j1 json_overview_image_info V=s || true",
1240 description="Calculating checksums",
1241 descriptionDone="Checksums calculated",
1242 command=["make", "-j1", "checksum", "V=s"],
1251 descriptionDone="Kmod directory created",
1256 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s",
1262 doStepIf=IsKmodArchiveEnabled,
1269 description="Preparing kmod archive",
1270 descriptionDone="Kmod archive prepared",
1273 "--include=/kmod-*.ipk",
1277 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/",
1282 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1288 doStepIf=IsKmodArchiveEnabled,
1295 description="Indexing kmod archive",
1296 descriptionDone="Kmod archive indexed",
1299 Interpolate("-j%(prop:nproc:-1)s"),
1302 "CONFIG_SIGNED_PACKAGES=",
1304 "PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1311 doStepIf=IsKmodArchiveEnabled,
1319 descriptionDone="Temporary signing directory prepared",
1320 command=["mkdir", "-p", "%s/signing" % (work_dir)],
1322 doStepIf=IsSignEnabled,
1329 description="Packing files to sign",
1330 descriptionDone="Files to sign packed",
1331 command=Interpolate(
1332 "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",
1337 doStepIf=IsSignEnabled,
1343 workersrc="sign.tar.gz",
1344 masterdest="%s/signing/%s.%s.tar.gz" % (work_dir, ts[0], ts[1]),
1346 doStepIf=IsSignEnabled,
1353 description="Signing files",
1354 descriptionDone="Files signed",
1356 "%s/signall.sh" % (scripts_dir),
1357 "%s/signing/%s.%s.tar.gz" % (work_dir, ts[0], ts[1]),
1358 Interpolate("%(prop:branch)s"),
1360 env={"CONFIG_INI": os.getenv("BUILDMASTER_CONFIG", "./config.ini")},
1362 doStepIf=IsSignEnabled,
1369 mastersrc="%s/signing/%s.%s.tar.gz" % (work_dir, ts[0], ts[1]),
1370 workerdest="sign.tar.gz",
1372 doStepIf=IsSignEnabled,
1379 description="Unpacking signed files",
1380 descriptionDone="Signed files unpacked",
1381 command=["tar", "-xzf", "sign.tar.gz"],
1383 doStepIf=IsSignEnabled,
1391 descriptionDone="Upload directory structure prepared",
1396 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s",
1399 prefix=GetVersionPrefix,
1409 descriptionDone="Repository symlink prepared",
1415 "../packages-%(kw:basever)s",
1416 basever=util.Transform(GetBaseVersion, Property("branch")),
1419 "tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix
1422 doStepIf=IsNoMasterBuild,
1429 name="kmoddirprepare",
1430 descriptionDone="Kmod archive upload directory prepared",
1435 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s",
1438 prefix=GetVersionPrefix,
1442 doStepIf=IsKmodArchiveEnabled,
1449 description="Uploading directory structure",
1450 descriptionDone="Directory structure uploaded",
1451 command=["rsync", Interpolate("-az%(prop:rsync_ipv4:+4)s")]
1455 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url")),
1458 "RSYNC_PASSWORD": Interpolate(
1459 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1465 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1469 # download remote sha256sums to 'target-sha256sums'
1472 name="target-sha256sums",
1473 description="Fetching remote sha256sums for target",
1474 descriptionDone="Remote sha256sums for target fetched",
1475 command=["rsync", Interpolate("-z%(prop:rsync_ipv4:+4)s")]
1479 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/sha256sums",
1480 url=GetRsyncParams.withArgs("bin", "url"),
1483 prefix=GetVersionPrefix,
1485 "target-sha256sums",
1488 "RSYNC_PASSWORD": Interpolate(
1489 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1493 haltOnFailure=False,
1494 flunkOnFailure=False,
1495 warnOnFailure=False,
1496 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1500 # build list of files to upload
1503 name="dlsha2rsyncpl",
1504 mastersrc=scripts_dir + "/sha2rsync.pl",
1505 workerdest="../sha2rsync.pl",
1513 description="Building list of files to upload",
1514 descriptionDone="List of files to upload built",
1517 "target-sha256sums",
1519 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1532 mastersrc=scripts_dir + "/rsync.sh",
1533 workerdest="../rsync.sh",
1538 # upload new files and update existing ones
1541 name="targetupload",
1542 description="Uploading target files",
1543 descriptionDone="Target files uploaded",
1546 "--exclude=/kmods/",
1547 "--files-from=rsynclist",
1549 "--partial-dir=.~tmp~%s~%s" % (ts[0], ts[1]),
1553 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1555 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1560 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1561 url=GetRsyncParams.withArgs("bin", "url"),
1564 prefix=GetVersionPrefix,
1568 "RSYNC_PASSWORD": Interpolate(
1569 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1574 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1578 # delete files which don't exist locally
1582 description="Pruning target files",
1583 descriptionDone="Target files pruned",
1586 "--exclude=/kmods/",
1589 "--ignore-existing",
1591 "--partial-dir=.~tmp~%s~%s" % (ts[0], ts[1]),
1595 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1597 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1602 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1603 url=GetRsyncParams.withArgs("bin", "url"),
1606 prefix=GetVersionPrefix,
1610 "RSYNC_PASSWORD": Interpolate(
1611 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1617 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1624 description="Uploading kmod archive",
1625 descriptionDone="Kmod archive uploaded",
1630 "--partial-dir=.~tmp~%s~%s" % (ts[0], ts[1]),
1634 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1636 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1641 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s/",
1642 url=GetRsyncParams.withArgs("bin", "url"),
1645 prefix=GetVersionPrefix,
1649 "RSYNC_PASSWORD": Interpolate(
1650 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1656 doStepIf=IsKmodArchiveAndRsyncEnabled,
1663 description="Finding source archives to upload",
1664 descriptionDone="Source archives to upload found",
1665 command="find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1672 name="sourceupload",
1673 description="Uploading source archives",
1674 descriptionDone="Source archives uploaded",
1677 "--files-from=sourcelist",
1684 "--partial-dir=.~tmp~%(kw:target)s~%(kw:subtarget)s~%(prop:workername)s",
1688 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1690 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("src", "url")),
1693 "RSYNC_PASSWORD": Interpolate(
1694 "%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")
1700 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("src", "url")),
1707 description="Reporting disk usage",
1708 command=["df", "-h", "."],
1709 env={"LC_ALL": "C"},
1711 haltOnFailure=False,
1712 flunkOnFailure=False,
1713 warnOnFailure=False,
1721 description="Reporting estimated file space usage",
1722 command=["du", "-sh", "."],
1723 env={"LC_ALL": "C"},
1725 haltOnFailure=False,
1726 flunkOnFailure=False,
1727 warnOnFailure=False,
1735 description="Reporting ccache stats",
1736 command=["ccache", "-s"],
1739 haltOnFailure=False,
1740 flunkOnFailure=False,
1741 warnOnFailure=False,
1742 doStepIf=util.Transform(bool, Property("ccache_command")),
1746 for brname in branchNames:
1747 bldrname = brname + "_" + target
1748 c["builders"].append(
1751 workernames=workerNames,
1756 nextBuild=GetNextBuild,
1757 canStartBuild=canStartBuild,
1762 ####### STATUS TARGETS
1764 # 'status' is a list of Status Targets. The results of each build will be
1765 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1766 # including web pages, email senders, and IRC bots.
1768 if "status_bind" in inip1:
1770 "port": inip1.get("status_bind"),
1771 "plugins": {"waterfall_view": True, "console_view": True, "grid_view": True},
1774 if "status_user" in inip1 and "status_password" in inip1:
1775 c["www"]["auth"] = util.UserPasswordAuth(
1776 [(inip1.get("status_user"), inip1.get("status_password"))]
1778 c["www"]["authz"] = util.Authz(
1779 allowRules=[util.AnyControlEndpointMatcher(role="admins")],
1781 util.RolesFromUsername(
1782 roles=["admins"], usernames=[inip1.get("status_user")]
1788 if ini.has_section("irc"):
1790 irc_host = iniirc.get("host", None)
1791 irc_port = iniirc.getint("port", 6667)
1792 irc_chan = iniirc.get("channel", None)
1793 irc_nick = iniirc.get("nickname", None)
1794 irc_pass = iniirc.get("password", None)
1796 if irc_host and irc_nick and irc_chan:
1797 irc = reporters.IRC(
1802 channels=[irc_chan],
1803 notify_events=["exception", "problem", "recovery"],
1806 c["services"].append(irc)
1808 c["revlink"] = util.RevlinkMatch(
1809 [r"https://git.openwrt.org/openwrt/(.*).git"],
1810 r"https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s",
1816 # This specifies what database buildbot uses to store its state. You can leave
1817 # this at its default for all but the largest installations.
1818 "db_url": "sqlite:///state.sqlite",
1821 c["buildbotNetUsageData"] = None