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.process import buildstep
21 from buildbot.plugins import reporters
22 from buildbot.plugins import schedulers
23 from buildbot.plugins import steps
24 from buildbot.plugins import util
25 from buildbot.process import properties
26 from buildbot.process import results
27 from buildbot.process.factory import BuildFactory
28 from buildbot.process.properties import Interpolate
29 from buildbot.process.properties import Property
30 from buildbot.schedulers.basic import AnyBranchScheduler
31 from buildbot.schedulers.forcesched import BaseParameter
32 from buildbot.schedulers.forcesched import ForceScheduler
33 from buildbot.schedulers.forcesched import ValidationError
34 from buildbot.steps.master import MasterShellCommand
35 from buildbot.steps.shell import SetPropertyFromCommand
36 from buildbot.steps.shell import ShellCommand
37 from buildbot.steps.source.git import Git
38 from buildbot.steps.transfer import FileDownload
39 from buildbot.steps.transfer import FileUpload
40 from buildbot.steps.transfer import StringDownload
41 from buildbot.worker import Worker
42 from buildbot.worker.local import LocalWorker
45 if not os.path.exists("twistd.pid"):
46 with open("twistd.pid", "w") as pidfile:
47 pidfile.write("{}".format(os.getpid()))
49 # This is a sample buildmaster config file. It must be installed as
50 # 'master.cfg' in your buildmaster's base directory.
52 ini = configparser.ConfigParser()
53 ini.read(os.getenv("BUILDMASTER_CONFIG", "./config.ini"))
55 if "general" not in ini or "phase1" not in ini:
56 raise ValueError("Fix your configuration")
61 work_dir = os.path.abspath(ini["general"].get("workdir", "."))
62 scripts_dir = os.path.abspath("../scripts")
64 repo_url = ini["repo"].get("url")
65 tree_stable_timer = ini["repo"].getint("tree_stable_timer", 15 * 60)
67 rsync_defopts = ["-v", "--timeout=120"]
69 # if rsync_bin_url.find("::") > 0 or rsync_bin_url.find("rsync://") == 0:
70 # rsync_bin_defopts += ["--contimeout=20"]
75 def ini_parse_branch(section):
77 name = section.get("name")
80 raise ValueError("missing 'name' in " + repr(section))
82 raise ValueError("duplicate branch name in " + repr(section))
85 b["bin_url"] = section.get("binary_url")
86 b["bin_key"] = section.get("binary_password")
88 b["src_url"] = section.get("source_url")
89 b["src_key"] = section.get("source_password")
91 b["gpg_key"] = section.get("gpg_key")
93 b["usign_key"] = section.get("usign_key")
94 usign_comment = "untrusted comment: " + name.replace("-", " ").title() + " key"
95 b["usign_comment"] = section.get("usign_comment", usign_comment)
97 b["config_seed"] = section.get("config_seed")
98 b["build_targets"] = section.get("build_targets")
100 b["kmod_archive"] = section.getboolean("kmod_archive", False)
103 log.msg("Configured branch: {}".format(name))
106 # PB port can be either a numeric port or a connection string
107 pb_port = inip1.get("port") or 9989
109 # This is the dictionary that the buildmaster pays attention to. We also use
110 # a shorter alias to save typing.
111 c = BuildmasterConfig = {}
115 # the 'title' string will appear at the top of this buildbot
116 # installation's html.WebStatus home page (linked to the
117 # 'titleURL') and is embedded in the title of the waterfall HTML page.
119 c["title"] = ini["general"].get("title")
120 c["titleURL"] = ini["general"].get("title_url")
122 # the 'buildbotURL' string should point to the location where the buildbot's
123 # internal web server (usually the html.WebStatus page) is visible. This
124 # typically uses the port number set in the Waterfall 'status' entry, but
125 # with an externally-visible host name which the buildbot cannot figure out
128 c["buildbotURL"] = inip1.get("buildbot_url")
132 # The 'workers' list defines the set of recognized buildworkers. Each element is
133 # a Worker object, specifying a unique worker name and password. The same
134 # worker name and password must be configured on the worker.
140 def ini_parse_workers(section):
141 name = section.get("name")
142 password = section.get("password")
143 phase = section.getint("phase")
144 tagonly = section.getboolean("tag_only")
145 rsyncipv4 = section.getboolean("rsync_ipv4")
147 if not name or not password or not phase == 1:
148 log.msg("invalid worker configuration ignored: {}".format(repr(section)))
151 sl_props = {"tag_only": tagonly}
152 if "dl_lock" in section:
153 lockname = section.get("dl_lock")
154 sl_props["dl_lock"] = lockname
155 if lockname not in NetLocks:
156 NetLocks[lockname] = locks.MasterLock(lockname)
157 if "ul_lock" in section:
158 lockname = section.get("ul_lock")
159 sl_props["ul_lock"] = lockname
160 if lockname not in NetLocks:
161 NetLocks[lockname] = locks.MasterLock(lockname)
165 ] = True # only set prop if required, we use '+' Interpolate substitution
167 log.msg("Configured worker: {}".format(name))
168 # NB: phase1 build factory requires workers to be single-build only
169 c["workers"].append(Worker(name, password, max_builds=1, properties=sl_props))
172 for section in ini.sections():
173 if section.startswith("branch "):
174 ini_parse_branch(ini[section])
176 if section.startswith("worker "):
177 ini_parse_workers(ini[section])
179 # list of branches in build-priority order
180 branchNames = [branches[b]["name"] for b in branches]
182 c["protocols"] = {"pb": {"port": pb_port}}
185 c["collapseRequests"] = True
187 # Reduce amount of backlog data
188 c["configurators"] = [
189 util.JanitorConfigurator(
190 logHorizon=timedelta(days=3),
196 @defer.inlineCallbacks
197 def getNewestCompleteTimePrio(bldr):
198 """Returns the priority and the complete_at of the latest completed and not SKIPPED
199 build request for this builder, or None if there are no such build
200 requests. We need to filter out SKIPPED requests because we're
201 using collapseRequests=True which is unfortunately marking all
202 previous requests as complete when new buildset is created.
204 @returns: (priority, datetime instance or None), via Deferred
207 prio = yield bldr.get_highest_priority()
211 bldrid = yield bldr.getBuilderId()
212 completed = yield bldr.master.data.get(
213 ("builders", bldrid, "buildrequests"),
215 resultspec.Filter("complete", "eq", [True]),
216 resultspec.Filter("results", "ne", [results.SKIPPED]),
218 order=["-complete_at"],
224 complete_at = completed[0]["complete_at"]
226 last_build = yield bldr.master.data.get(
229 resultspec.Filter("builderid", "eq", [bldrid]),
231 order=["-started_at"],
235 if last_build and last_build[0]:
236 last_complete_at = last_build[0]["complete_at"]
237 if last_complete_at and (last_complete_at > complete_at):
238 return (prio, last_complete_at)
240 return (prio, complete_at)
243 @defer.inlineCallbacks
244 def prioritizeBuilders(master, builders):
245 """Returns sorted list of builders by their last timestamp of completed and
246 not skipped build, ordered first by branch name.
248 @returns: list of sorted builders
251 bldrNamePrio = {"__Janitor": 0, "00_force_build": 0}
253 for bname in branchNames:
254 bldrNamePrio[bname] = i
256 def is_building(bldr):
257 return bool(bldr.building) or bool(bldr.old_building)
260 d = defer.maybeDeferred(getNewestCompleteTimePrio, bldr)
261 d.addCallback(lambda retval: (retval, bldr))
265 ((hiprio, complete_at), bldr) = item
267 # check if we have some high prio build requests pending (i.e. tag builds),
268 # if so, front-run these builders, while preserving the per-branch static priority
270 for name, prio in bldrNamePrio.items():
271 if bldr.name.startswith(name):
272 # higher priority (larger positive number) raises position
273 pos = prio + 50 - min(hiprio, 50)
276 # pos order: janitor/local (0), tag builds if any [1..50], !tag builds [51...]
280 complete_at = date.replace(tzinfo=tzutc())
282 if is_building(bldr):
284 complete_at = date.replace(tzinfo=tzutc())
286 return (pos, complete_at, bldr.name)
288 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
289 results.sort(key=bldr_sort)
292 # log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
294 return [r[1] for r in results]
297 c["prioritizeBuilders"] = prioritizeBuilders
305 def populateTargets():
306 def buildTargetsConfigured(branch):
307 builders = branches[branch].get("build_targets")
308 return builders and set(filter(None, [t.strip() for t in builders.split("\n")]))
310 for branch in branchNames:
311 targets[branch] = buildTargetsConfigured(branch)
312 if not targets[branch]:
313 populateTargetsForBranch(branch)
316 def populateTargetsForBranch(branch):
317 """fetches a shallow clone for passed `branch` and then
318 executes dump-target-info.pl and collates the results to ensure
319 targets that only exist in specific branches get built.
320 This takes a while during master startup but is executed only once.
322 targets[branch] = set()
323 sourcegit = work_dir + "/source.git"
325 log.msg(f"Populating targets for {branch}, this will take time")
327 if os.path.isdir(sourcegit):
328 subprocess.call(["rm", "-rf", sourcegit])
336 "--branch=" + branch,
342 os.makedirs(sourcegit + "/tmp", exist_ok=True)
343 findtargets = subprocess.Popen(
344 ["./scripts/dump-target-info.pl", "targets"],
345 stdout=subprocess.PIPE,
346 stderr=subprocess.DEVNULL,
351 line = findtargets.stdout.readline()
354 ta = line.decode().strip().split(" ")
355 targets[branch].add(ta[0])
357 subprocess.call(["rm", "-rf", sourcegit])
362 # the 'change_source' setting tells the buildmaster how it should find out
363 # about source code changes.
365 c["change_source"] = []
366 c["change_source"].append(
369 workdir=work_dir + "/work.git",
370 branches=branchNames,
378 # Configure the Schedulers, which decide how to react to incoming changes.
381 # Selector for known valid tags
382 class TagChoiceParameter(BaseParameter):
383 spec_attributes = ["strict", "choices"]
387 def __init__(self, name, label=None, **kw):
388 super().__init__(name, label, **kw)
389 self._choice_list = []
391 def getRevTags(self, findtag=None):
395 # we will filter out tags that do no match the configured branches
396 for b in branchNames:
397 basever = re.search(r"-([0-9]+\.[0-9]+)$", b)
399 branchvers.append(basever[1])
401 # grab tags from remote repository
402 alltags = subprocess.Popen(
403 ["git", "ls-remote", "--tags", repo_url], stdout=subprocess.PIPE
407 line = alltags.stdout.readline()
412 (rev, tag) = line.split()
414 # does it match known format? ('vNN.NN.NN(-rcN)')
416 r"\brefs/tags/(v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$",
417 tag.decode().strip(),
420 # only list valid tags matching configured branches
421 if tagver and any(tagver[1][1:].startswith(b) for b in branchvers):
422 # if we want a specific tag, ignore all that don't match
423 if findtag and findtag != tagver[1]:
425 taglist.append({"rev": rev.decode().strip(), "tag": tagver[1]})
431 taglist = [rt["tag"] for rt in self.getRevTags()]
434 key=lambda tag: tag if re.search(r"-rc[0-9]+$", tag) else tag + "-z",
436 taglist.insert(0, "")
438 self._choice_list = taglist
440 return self._choice_list
442 def updateFromKwargs(self, properties, kwargs, **unused):
443 tag = self.getFromKwargs(kwargs)
444 properties[self.name] = tag
446 # find the commit matching the tag
447 findtag = self.getRevTags(tag)
450 raise ValidationError("Couldn't find tag")
452 properties["force_revision"] = findtag[0]["rev"]
454 # find the branch matching the tag
456 branchver = re.search(r"v([0-9]+\.[0-9]+)", tag)
457 for b in branchNames:
458 if b.endswith(branchver[1]):
462 raise ValidationError("Couldn't find branch")
464 properties["force_branch"] = branch
466 def parse_from_arg(self, s):
467 if self.strict and s not in self._choice_list:
468 raise ValidationError(
469 "'%s' does not belong to list of available choices '%s'"
470 % (s, self._choice_list)
476 @defer.inlineCallbacks
477 def builderNames(props):
478 """since we have per branch and per target builders,
479 address the relevant builder for each new buildrequest
480 based on the request's desired branch and target.
482 branch = props.getProperty("branch")
483 target = props.getProperty("target", "")
488 # if that didn't work, try sourcestamp to find a branch
490 # match builders with target branch
491 ss = props.sourcestamps[0]
493 branch = ss["branch"]
495 log.msg("couldn't find builder")
496 return [] # nothing works
498 bname = branch + "_" + target
501 for b in (yield props.master.data.get(("builders",))):
502 if not b["name"].startswith(bname):
504 builders.append(b["name"])
510 c["schedulers"].append(
513 change_filter=util.ChangeFilter(branch=branchNames),
514 treeStableTimer=tree_stable_timer,
515 builderNames=builderNames,
519 c["schedulers"].append(
522 buttonName="Force builds",
523 label="Force build details",
524 builderNames=["00_force_build"],
526 util.CodebaseParameter(
529 branch=util.FixedParameter(name="branch", default=""),
530 revision=util.FixedParameter(name="revision", default=""),
531 repository=util.FixedParameter(name="repository", default=""),
532 project=util.FixedParameter(name="project", default=""),
535 reason=util.StringParameter(
538 default="Trigger build",
543 # NB: avoid nesting to simplify processing of properties
544 util.ChoiceStringParameter(
546 label="Build target",
548 choices=["all"] + [t for b in branchNames for t in targets[b]],
550 TagChoiceParameter(name="tag", label="Build tag", default=""),
555 c["schedulers"].append(
556 schedulers.Triggerable(name="trigger", builderNames=builderNames, priority=20)
561 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
562 # what steps, and which workers can execute them. Note that any particular build will
563 # only take place on one worker.
566 def IsNoMasterBuild(step):
567 branch = step.getProperty("branch")
568 return branch not in ["main", "master"]
571 def IsUsignEnabled(step):
572 branch = step.getProperty("branch")
573 return branch and branches[branch].get("usign_key")
576 def IsSignEnabled(step):
577 branch = step.getProperty("branch")
578 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
581 def IsKmodArchiveEnabled(step):
582 branch = step.getProperty("branch")
583 return branch and branches[branch].get("kmod_archive")
586 def IsKmodArchiveAndRsyncEnabled(step):
587 branch = step.getProperty("branch")
588 return bool(IsKmodArchiveEnabled(step) and branches[branch].get("bin_url"))
591 def IsRemoteShaSumsAvailable(step):
592 return step.getProperty("have_remote_shasums")
595 def GetBaseVersion(branch):
596 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
597 return branch.split("-")[1]
603 def GetVersionPrefix(props):
604 branch = props.getProperty("branch")
605 basever = GetBaseVersion(branch)
606 if props.hasProperty("tag") and re.match(
607 r"^v[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]
609 return "%s/" % props["tag"][1:]
610 elif basever != "main":
611 return "%s-SNAPSHOT/" % basever
617 def GetConfigSeed(props):
618 branch = props.getProperty("branch")
619 return branch and branches[branch].get("config_seed") or ""
623 def GetRsyncParams(props, srcorbin, urlorkey):
624 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
625 branch = props.getProperty("branch")
626 opt = srcorbin + "_" + urlorkey
627 return branch and branches[branch].get(opt)
631 def GetUsignKey(props):
632 branch = props.getProperty("branch")
633 return branch and branches[branch].get("usign_key")
636 def GetNextBuild(builder, requests):
639 # order tagged build first
640 if r.properties.hasProperty("tag"):
644 # log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
648 def MakeEnv(overrides=None, tryccache=False):
650 "CCC": Interpolate("%(prop:cc_command:-gcc)s"),
651 "CCXX": Interpolate("%(prop:cxx_command:-g++)s"),
654 env["CC"] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
655 env["CXX"] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
656 env["CCACHE"] = Interpolate("%(prop:ccache_command:-)s")
658 env["CC"] = env["CCC"]
659 env["CXX"] = env["CCXX"]
661 if overrides is not None:
662 env.update(overrides)
667 def NetLockDl(props, extralock=None):
669 if props.hasProperty("dl_lock"):
670 lock = NetLocks[props["dl_lock"]]
672 return [lock.access("exclusive")]
678 def NetLockUl(props):
680 if props.hasProperty("ul_lock"):
681 lock = NetLocks[props["ul_lock"]]
683 return [lock.access("exclusive")]
688 def IsTargetSelected(target):
689 def CheckTargetProperty(step):
690 selected_target = step.getProperty("target", "all")
691 if selected_target != "all" and selected_target != target:
695 return CheckTargetProperty
699 def UsignSec2Pub(props):
700 branch = props.getProperty("branch")
703 branches[branch].get("usign_comment") or "untrusted comment: secret key"
705 seckey = branches[branch].get("usign_key")
706 seckey = base64.b64decode(seckey)
710 return "{}\n{}".format(
711 re.sub(r"\bsecret key$", "public key", comment),
712 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]),
716 def canStartBuild(builder, wfb, request):
717 """filter out non tag requests for tag_only workers."""
718 wtagonly = wfb.worker.properties.getProperty("tag_only")
719 tag = request.properties.getProperty("tag")
721 if wtagonly and not tag:
731 for worker in c["workers"]:
732 workerNames.append(worker.workername)
734 # add a single LocalWorker to handle the forcebuild builder
735 c["workers"].append(LocalWorker("__local_force_build", max_builds=1))
737 force_factory = BuildFactory()
738 force_factory.addStep(
740 name="trigger_build",
741 schedulerNames=["trigger"],
745 "branch": Property("force_branch"),
746 "revision": Property("force_revision"),
747 "repository": repo_url,
752 "reason": Property("reason"),
753 "tag": Property("tag"),
754 "target": Property("target"),
759 c["builders"].append(
761 name="00_force_build", workername="__local_force_build", factory=force_factory
768 # Extension of ShellCommand and sets in property:
769 # - True: the command succeded
770 # - False: the command failed
771 class ShellCommandAndSetProperty(buildstep.ShellMixin, buildstep.BuildStep):
772 name = "shellandsetproperty"
773 renderables = ['property']
780 kwargs = self.setupShellMixin(kwargs)
782 self.property = property
784 super().__init__(**kwargs)
786 @defer.inlineCallbacks
788 cmd = yield self.makeRemoteShellCommand()
790 yield self.runCommand(cmd)
792 self.setProperty(self.property, not cmd.didFail(), "ShellCommandAndSetProperty Step")
797 # NB the phase1 build factory assumes workers are single-build only
798 def prepareFactory(target):
799 (target, subtarget) = target.split("/")
801 factory = BuildFactory()
803 # setup shared work directory if required
807 descriptionDone="Shared work directory set up",
808 command='test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
814 # find number of cores
816 SetPropertyFromCommand(
819 description="Finding number of CPUs",
824 # find gcc and g++ compilers
828 mastersrc=scripts_dir + "/findbin.pl",
829 workerdest="../findbin.pl",
835 SetPropertyFromCommand(
837 property="cc_command",
838 description="Finding gcc command",
839 command=["../findbin.pl", "gcc", "", ""],
845 SetPropertyFromCommand(
847 property="cxx_command",
848 description="Finding g++ command",
849 command=["../findbin.pl", "g++", "", ""],
854 # see if ccache is available
856 SetPropertyFromCommand(
858 property="ccache_command",
859 description="Testing for ccache command",
860 command=["which", "ccache"],
862 flunkOnFailure=False,
864 hideStepIf=lambda r, s: r == results.FAILURE,
868 # check out the source
870 # if repo doesn't exist: 'git clone repourl'
871 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -f -f -d -x'. Only works with mode='full'
872 # git cat-file -e <commit>
873 # git checkout -f <commit>
874 # git checkout -B <branch>
887 # workaround for https://github.com/openwrt/buildbot/issues/5
890 name="git me once more please",
903 description="Fetching Git remote refs",
904 descriptionDone="Git remote refs fetched",
910 "+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s"
917 # getver.sh requires local branches to track upstream otherwise version computation fails.
918 # Git() does not set tracking branches when cloning or switching, so work around this here
921 name="trackupstream",
922 description="Setting upstream branch",
923 descriptionDone="getver.sh is happy now",
924 command=["git", "branch", "-u", Interpolate("origin/%(prop:branch)s")],
929 # Verify that Git HEAD points to a tag or branch
930 # Ref: https://web.archive.org/web/20190729224316/http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
934 description="Ensuring that Git HEAD is pointing to a branch or tag",
935 descriptionDone="Git HEAD is sane",
937 "git rev-parse --abbrev-ref HEAD | grep -vxqF HEAD || "
938 "git show-ref --tags --dereference 2>/dev/null | sed -ne "
939 '"/^$(git rev-parse HEAD) / { s|^.*/||; s|\\^.*||; p }" | grep -qE "^v[0-9][0-9]\\."'
948 s='#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
949 workerdest="../ccache_cc.sh",
957 s='#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
958 workerdest="../ccache_cxx.sh",
967 description="Updating feeds",
968 command=["./scripts/feeds", "update"],
969 env=MakeEnv(tryccache=True),
979 description="Installing feeds",
980 command=["./scripts/feeds", "install", "-a"],
981 env=MakeEnv(tryccache=True),
990 s=Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
991 workerdest=".config",
1000 descriptionDone=".config seeded",
1001 command=Interpolate(
1002 "printf 'CONFIG_TARGET_%(kw:target)s=y\\n"
1003 "CONFIG_TARGET_%(kw:target)s_%(kw:subtarget)s=y\\n"
1004 "CONFIG_SIGNED_PACKAGES=%(kw:usign:#?|y|n)s\\n' >> .config",
1006 subtarget=subtarget,
1015 description="Populating .config",
1016 command=["make", "defconfig"],
1021 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
1025 description="Checking architecture",
1026 descriptionDone="Architecture validated",
1027 command='grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config'
1028 % (target, subtarget),
1033 flunkOnFailure=False, # this is not a build FAILURE - TODO mark build as SKIPPED
1039 SetPropertyFromCommand(
1042 description="Finding libc suffix",
1046 '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }',
1055 name="dlkeybuildpub",
1056 s=Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
1057 workerdest="key-build.pub",
1059 doStepIf=IsUsignEnabled,
1066 s="# fake private key",
1067 workerdest="key-build",
1069 doStepIf=IsUsignEnabled,
1075 name="dlkeybuilducert",
1076 s="# fake certificate",
1077 workerdest="key-build.ucert",
1079 doStepIf=IsUsignEnabled,
1087 description="Preparing dl/",
1088 descriptionDone="dl/ prepared",
1089 command='mkdir -p ../dl && rm -rf "build/dl" && ln -s ../../dl "build/dl"',
1090 workdir=Property("builddir"),
1100 description="Pruning dl/",
1101 descriptionDone="dl/ pruned",
1102 command="find dl/ -mindepth 1 -atime +15 -delete -print",
1104 haltOnFailure=False,
1105 flunkOnFailure=False,
1106 warnOnFailure=False,
1114 description="Building and installing GNU tar",
1115 descriptionDone="GNU tar built and installed",
1118 Interpolate("-j%(prop:nproc:-1)s"),
1119 "tools/tar/compile",
1122 env=MakeEnv(tryccache=True),
1131 description="Populating dl/",
1132 descriptionDone="dl/ populated",
1133 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
1143 description="Cleaning base-files",
1144 command=["make", "package/base-files/clean", "V=s"],
1152 description="Building and installing tools",
1153 descriptionDone="Tools built and installed",
1156 Interpolate("-j%(prop:nproc:-1)s"),
1160 env=MakeEnv(tryccache=True),
1168 description="Building and installing toolchain",
1169 descriptionDone="Toolchain built and installed",
1172 Interpolate("-j%(prop:nproc:-1)s"),
1173 "toolchain/install",
1184 description="Building kmods",
1185 descriptionDone="Kmods built",
1188 Interpolate("-j%(prop:nproc:-1)s"),
1191 "IGNORE_ERRORS=n m",
1199 # find kernel version
1201 SetPropertyFromCommand(
1202 name="kernelversion",
1203 property="kernelversion",
1204 description="Finding the effective Kernel version",
1206 "make --no-print-directory -C target/linux/ "
1207 "val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | "
1208 "xargs printf '%s-%s-%s\\n'"
1210 env={"TOPDIR": Interpolate("%(prop:builddir)s/build")},
1217 description="Cleaning up package build",
1218 descriptionDone="Package build cleaned up",
1219 command=["make", "package/cleanup", "V=s"],
1226 description="Building packages",
1227 descriptionDone="Packages built",
1230 Interpolate("-j%(prop:nproc:-1)s"),
1233 "IGNORE_ERRORS=n m",
1244 description="Installing packages",
1245 descriptionDone="Packages installed",
1248 Interpolate("-j%(prop:nproc:-1)s"),
1260 description="Building and installing images",
1261 descriptionDone="Images built and installed",
1264 Interpolate("-j%(prop:nproc:-1)s"),
1276 description="Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
1277 command="make -j1 buildinfo V=s || true",
1285 name="json_overview_image_info",
1286 description="Generating profiles.json in target folder",
1287 command="make -j1 json_overview_image_info V=s || true",
1296 descriptionDone="Kmod directory created",
1301 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s",
1303 subtarget=subtarget,
1307 doStepIf=IsKmodArchiveEnabled,
1314 description="Preparing kmod archive",
1315 descriptionDone="Kmod archive prepared",
1318 "--remove-source-files",
1319 "--include=/kmod-*.ipk",
1320 "--include=/kmod-*.apk",
1324 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/",
1326 subtarget=subtarget,
1329 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1331 subtarget=subtarget,
1335 doStepIf=IsKmodArchiveEnabled,
1342 description="Indexing packages",
1343 descriptionDone="Packages indexed",
1346 Interpolate("-j%(prop:nproc:-1)s"),
1349 "CONFIG_SIGNED_PACKAGES=",
1359 description="Indexing kmod archive",
1360 descriptionDone="Kmod archive indexed",
1363 Interpolate("-j%(prop:nproc:-1)s"),
1366 "CONFIG_SIGNED_PACKAGES=",
1368 "PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1370 subtarget=subtarget,
1375 doStepIf=IsKmodArchiveEnabled,
1382 description="Calculating checksums",
1383 descriptionDone="Checksums calculated",
1384 command=["make", "-j1", "checksum", "V=s"],
1390 # download remote sha256sums to 'target-sha256sums'
1392 ShellCommandAndSetProperty(
1393 name="target-sha256sums",
1394 description="Fetching remote sha256sums for target",
1395 descriptionDone="Remote sha256sums for target fetched",
1396 command=["rsync", Interpolate("-z%(prop:rsync_ipv4:+4)s")]
1400 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/sha256sums",
1401 url=GetRsyncParams.withArgs("bin", "url"),
1403 subtarget=subtarget,
1404 prefix=GetVersionPrefix,
1406 "target-sha256sums",
1409 "RSYNC_PASSWORD": Interpolate(
1410 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1413 property="have_remote_shasums",
1415 haltOnFailure=False,
1416 flunkOnFailure=False,
1417 warnOnFailure=False,
1418 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1424 name="target-sha256sums_kmodsparse",
1425 description="Extract kmods from remote sha256sums",
1426 descriptionDone="Kmods extracted",
1427 command="sed \"/ \\*kmods\\//! d\" target-sha256sums | tee target-sha256sums-kmods",
1428 haltOnFailure=False,
1429 doStepIf=IsRemoteShaSumsAvailable,
1435 name="mergesha256sum",
1436 description="Merge sha256sums kmods with sha256sums",
1437 descriptionDone="Sha256sums merged",
1444 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1446 subtarget=subtarget,
1448 "target-sha256sums-kmods",
1451 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1453 subtarget=subtarget,
1456 env={"LC_ALL": "C"},
1457 haltOnFailure=False,
1458 doStepIf=IsRemoteShaSumsAvailable,
1466 descriptionDone="Temporary signing directory prepared",
1467 command=["mkdir", "-p", "%s/signing" % (work_dir)],
1469 doStepIf=IsSignEnabled,
1476 description="Packing files to sign",
1477 descriptionDone="Files to sign packed",
1478 command=Interpolate(
1479 "find bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/ "
1480 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/ "
1481 "-mindepth 1 -maxdepth 2 -type f -name sha256sums -print0 -or "
1482 "-name Packages -print0 -or -name packages.adb -print0 "
1483 "| xargs -0 tar -czf sign.tar.gz",
1485 subtarget=subtarget,
1488 doStepIf=IsSignEnabled,
1494 workersrc="sign.tar.gz",
1495 masterdest="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1497 doStepIf=IsSignEnabled,
1504 description="Signing files",
1505 descriptionDone="Files signed",
1507 "%s/signall.sh" % (scripts_dir),
1508 "%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1509 Interpolate("%(prop:branch)s"),
1511 env={"CONFIG_INI": os.getenv("BUILDMASTER_CONFIG", "./config.ini")},
1513 doStepIf=IsSignEnabled,
1520 mastersrc="%s/signing/%s.%s.tar.gz" % (work_dir, target, subtarget),
1521 workerdest="sign.tar.gz",
1523 doStepIf=IsSignEnabled,
1530 description="Unpacking signed files",
1531 descriptionDone="Signed files unpacked",
1532 command=["tar", "-xzf", "sign.tar.gz"],
1534 doStepIf=IsSignEnabled,
1542 descriptionDone="Upload directory structure prepared",
1547 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s",
1549 subtarget=subtarget,
1550 prefix=GetVersionPrefix,
1560 descriptionDone="Repository symlink prepared",
1566 "../packages-%(kw:basever)s",
1567 basever=util.Transform(GetBaseVersion, Property("branch")),
1570 "tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix
1573 doStepIf=IsNoMasterBuild,
1580 name="kmoddirprepare",
1581 descriptionDone="Kmod archive upload directory prepared",
1586 "tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s",
1588 subtarget=subtarget,
1589 prefix=GetVersionPrefix,
1593 doStepIf=IsKmodArchiveEnabled,
1600 description="Uploading directory structure",
1601 descriptionDone="Directory structure uploaded",
1602 command=["rsync", Interpolate("-az%(prop:rsync_ipv4:+4)s")]
1606 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url")),
1609 "RSYNC_PASSWORD": Interpolate(
1610 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1616 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1620 # build list of files to upload
1623 name="dlsha2rsyncpl",
1624 mastersrc=scripts_dir + "/sha2rsync.pl",
1625 workerdest="../sha2rsync.pl",
1633 description="Building list of files to upload",
1634 descriptionDone="List of files to upload built",
1637 "target-sha256sums",
1639 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums",
1641 subtarget=subtarget,
1652 mastersrc=scripts_dir + "/rsync.sh",
1653 workerdest="../rsync.sh",
1658 # upload new files and update existing ones
1661 name="targetupload",
1662 description="Uploading target files",
1663 descriptionDone="Target files uploaded",
1666 "--exclude=/kmods/",
1667 "--exclude=/kmods/**",
1668 "--files-from=rsynclist",
1670 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1674 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1676 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1678 subtarget=subtarget,
1681 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1682 url=GetRsyncParams.withArgs("bin", "url"),
1684 subtarget=subtarget,
1685 prefix=GetVersionPrefix,
1689 "RSYNC_PASSWORD": Interpolate(
1690 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1695 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1699 # delete files which don't exist locally
1703 description="Pruning target files",
1704 descriptionDone="Target files pruned",
1707 "--exclude=/kmods/",
1710 "--ignore-existing",
1712 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1716 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1718 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/",
1720 subtarget=subtarget,
1723 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/",
1724 url=GetRsyncParams.withArgs("bin", "url"),
1726 subtarget=subtarget,
1727 prefix=GetVersionPrefix,
1731 "RSYNC_PASSWORD": Interpolate(
1732 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1738 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("bin", "url")),
1745 description="Uploading kmod archive",
1746 descriptionDone="Kmod archive uploaded",
1751 "--partial-dir=.~tmp~%s~%s" % (target, subtarget),
1755 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1757 "bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/",
1759 subtarget=subtarget,
1762 "%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s/",
1763 url=GetRsyncParams.withArgs("bin", "url"),
1765 subtarget=subtarget,
1766 prefix=GetVersionPrefix,
1770 "RSYNC_PASSWORD": Interpolate(
1771 "%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")
1777 doStepIf=IsKmodArchiveAndRsyncEnabled,
1784 description="Finding source archives to upload",
1785 descriptionDone="Source archives to upload found",
1787 "find dl/ -maxdepth 1 -type f -not -size 0 "
1788 "-not -name '.*' -not -name '*.hash' -not -name "
1789 "'*.dl' -newer .config -printf '%f\\n' > sourcelist"
1797 name="sourceupload",
1798 description="Uploading source archives",
1799 descriptionDone="Source archives uploaded",
1802 "--files-from=sourcelist",
1809 "--partial-dir=.~tmp~%(kw:target)s~%(kw:subtarget)s~%(prop:workername)s",
1811 subtarget=subtarget,
1813 Interpolate("-a%(prop:rsync_ipv4:+4)s"),
1815 Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("src", "url")),
1818 "RSYNC_PASSWORD": Interpolate(
1819 "%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")
1825 doStepIf=util.Transform(bool, GetRsyncParams.withArgs("src", "url")),
1832 description="Reporting disk usage",
1833 command=["df", "-h", "."],
1834 env={"LC_ALL": "C"},
1836 haltOnFailure=False,
1837 flunkOnFailure=False,
1838 warnOnFailure=False,
1846 description="Reporting estimated file space usage",
1847 command=["du", "-sh", "."],
1848 env={"LC_ALL": "C"},
1850 haltOnFailure=False,
1851 flunkOnFailure=False,
1852 warnOnFailure=False,
1860 description="Reporting ccache stats",
1861 command=["ccache", "-s"],
1864 haltOnFailure=False,
1865 flunkOnFailure=False,
1866 warnOnFailure=False,
1867 doStepIf=util.Transform(bool, Property("ccache_command")),
1874 for brname in branchNames:
1875 for target in targets[brname]:
1876 bldrname = brname + "_" + target
1877 c["builders"].append(
1880 workernames=workerNames,
1881 factory=prepareFactory(target),
1885 nextBuild=GetNextBuild,
1886 canStartBuild=canStartBuild,
1893 # 'status' is a list of Status Targets. The results of each build will be
1894 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1895 # including web pages, email senders, and IRC bots.
1897 if "status_bind" in inip1:
1899 "port": inip1.get("status_bind"),
1900 "plugins": {"waterfall_view": True, "console_view": True, "grid_view": True},
1903 if "status_user" in inip1 and "status_password" in inip1:
1904 c["www"]["auth"] = util.UserPasswordAuth(
1905 [(inip1.get("status_user"), inip1.get("status_password"))]
1907 c["www"]["authz"] = util.Authz(
1908 allowRules=[util.AnyControlEndpointMatcher(role="admins")],
1910 util.RolesFromUsername(
1911 roles=["admins"], usernames=[inip1.get("status_user")]
1917 if ini.has_section("irc"):
1919 irc_host = iniirc.get("host", None)
1920 irc_port = iniirc.getint("port", 6667)
1921 irc_chan = iniirc.get("channel", None)
1922 irc_nick = iniirc.get("nickname", None)
1923 irc_pass = iniirc.get("password", None)
1925 if irc_host and irc_nick and irc_chan:
1926 irc = reporters.IRC(
1931 channels=[irc_chan],
1932 notify_events=["exception", "problem", "recovery"],
1935 c["services"].append(irc)
1937 c["revlink"] = util.RevlinkMatch(
1938 [r"https://git.openwrt.org/openwrt/(.*).git"],
1939 r"https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s",
1945 # This specifies what database buildbot uses to store its state. You can leave
1946 # this at its default for all but the largest installations.
1947 "db_url": "sqlite:///state.sqlite",
1950 c["buildbotNetUsageData"] = None