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 import filter
19 from buildbot.changes.gitpoller import GitPoller
20 from buildbot.config import BuilderConfig
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 SingleBranchScheduler
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
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")
64 repo_branch = ini['repo'].get("branch", "master")
66 rsync_defopts = ["-v", "-4", "--timeout=120"]
68 #if rsync_bin_url.find("::") > 0 or rsync_bin_url.find("rsync://") == 0:
69 # 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))
102 # PB port can be either a numeric port or a connection string
103 pb_port = inip1.get("port") or 9989
105 # This is the dictionary that the buildmaster pays attention to. We also use
106 # a shorter alias to save typing.
107 c = BuildmasterConfig = {}
109 ####### PROJECT IDENTITY
111 # the 'title' string will appear at the top of this buildbot
112 # installation's html.WebStatus home page (linked to the
113 # 'titleURL') and is embedded in the title of the waterfall HTML page.
115 c['title'] = ini['general'].get("title")
116 c['titleURL'] = ini['general'].get("title_url")
118 # the 'buildbotURL' string should point to the location where the buildbot's
119 # internal web server (usually the html.WebStatus page) is visible. This
120 # typically uses the port number set in the Waterfall 'status' entry, but
121 # with an externally-visible host name which the buildbot cannot figure out
124 c['buildbotURL'] = inip1.get("buildbot_url")
128 # The 'workers' list defines the set of recognized buildworkers. Each element is
129 # a Worker object, specifying a unique worker name and password. The same
130 # worker name and password must be configured on the worker.
135 for section in ini.sections():
136 if section.startswith("branch "):
137 ini_parse_branch(ini[section])
139 if section.startswith("worker "):
140 if ini.has_option(section, "name") and ini.has_option(section, "password") and \
141 (not ini.has_option(section, "phase") or ini.getint(section, "phase") == 1):
142 sl_props = { 'dl_lock':None, 'ul_lock':None }
143 name = ini.get(section, "name")
144 password = ini.get(section, "password")
145 if ini.has_option(section, "dl_lock"):
146 lockname = ini.get(section, "dl_lock")
147 sl_props['dl_lock'] = lockname
148 if lockname not in NetLocks:
149 NetLocks[lockname] = locks.MasterLock(lockname)
150 if ini.has_option(section, "ul_lock"):
151 lockname = ini.get(section, "ul_lock")
152 sl_props['ul_lock'] = lockname
153 if lockname not in NetLocks:
154 NetLocks[lockname] = locks.MasterLock(lockname)
155 c['workers'].append(Worker(name, password, max_builds = 1, properties = sl_props))
157 c['protocols'] = {'pb': {'port': pb_port}}
160 c['collapseRequests'] = True
162 # Reduce amount of backlog data
163 c['configurators'] = [util.JanitorConfigurator(
164 logHorizon=timedelta(days=3),
168 @defer.inlineCallbacks
169 def getNewestCompleteTime(bldr):
170 """Returns the complete_at of the latest completed and not SKIPPED
171 build request for this builder, or None if there are no such build
172 requests. We need to filter out SKIPPED requests because we're
173 using collapseRequests=True which is unfortunately marking all
174 previous requests as complete when new buildset is created.
176 @returns: datetime instance or None, via Deferred
179 bldrid = yield bldr.getBuilderId()
180 completed = yield bldr.master.data.get(
181 ('builders', bldrid, 'buildrequests'),
183 resultspec.Filter('complete', 'eq', [True]),
184 resultspec.Filter('results', 'ne', [results.SKIPPED]),
186 order=['-complete_at'], limit=1)
190 complete_at = completed[0]['complete_at']
192 last_build = yield bldr.master.data.get(
195 resultspec.Filter('builderid', 'eq', [bldrid]),
197 order=['-started_at'], limit=1)
199 if last_build and last_build[0]:
200 last_complete_at = last_build[0]['complete_at']
201 if last_complete_at and (last_complete_at > complete_at):
202 return last_complete_at
206 @defer.inlineCallbacks
207 def prioritizeBuilders(master, builders):
208 """Returns sorted list of builders by their last timestamp of completed and
211 @returns: list of sorted builders
214 def is_building(bldr):
215 return bool(bldr.building) or bool(bldr.old_building)
218 d = defer.maybeDeferred(getNewestCompleteTime, bldr)
219 d.addCallback(lambda complete_at: (complete_at, bldr))
223 (complete_at, bldr) = item
227 complete_at = date.replace(tzinfo=tzutc())
229 if is_building(bldr):
231 complete_at = date.replace(tzinfo=tzutc())
233 return (complete_at, bldr.name)
235 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
236 results.sort(key=bldr_sort)
239 log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
241 return [r[1] for r in results]
243 c['prioritizeBuilders'] = prioritizeBuilders
245 ####### CHANGESOURCES
247 branchNames = [branches[b]["name"] for b in branches]
252 def populateTargets():
253 log.msg("Populating targets, this will take time")
254 sourcegit = work_dir + '/source.git'
255 for branch in branchNames:
256 if os.path.isdir(sourcegit):
257 subprocess.call(["rm", "-rf", sourcegit])
259 subprocess.call(["git", "clone", "--depth=1", "--branch="+branch, repo_url, sourcegit])
261 os.makedirs(sourcegit + '/tmp', exist_ok=True)
262 findtargets = subprocess.Popen(['./scripts/dump-target-info.pl', 'targets'],
263 stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, cwd = sourcegit)
266 line = findtargets.stdout.readline()
269 ta = line.decode().strip().split(' ')
272 subprocess.call(["rm", "-rf", sourcegit])
276 # the 'change_source' setting tells the buildmaster how it should find out
277 # about source code changes. Here we point to the buildbot clone of pyflakes.
279 c['change_source'] = []
280 c['change_source'].append(GitPoller(
282 workdir=work_dir+'/work.git', branch=repo_branch,
287 # Configure the Schedulers, which decide how to react to incoming changes. In this
288 # case, just kick off a 'basebuild' build
290 class TagChoiceParameter(BaseParameter):
291 spec_attributes = ["strict", "choices"]
295 def __init__(self, name, label=None, **kw):
296 super().__init__(name, label, **kw)
297 self._choice_list = []
302 basever = re.search(r'-([0-9]+\.[0-9]+)$', repo_branch)
305 findtags = subprocess.Popen(
306 ['git', 'ls-remote', '--tags', repo_url],
307 stdout = subprocess.PIPE)
310 line = findtags.stdout.readline()
315 tagver = re.search(r'\brefs/tags/v([0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$', line.decode().strip())
317 if tagver and tagver[1].find(basever[1]) == 0:
318 taglist.append(tagver[1])
320 taglist.sort(reverse=True, key=lambda tag: tag if re.search(r'-rc[0-9]+$', tag) else tag + '-z')
321 taglist.insert(0, '')
323 self._choice_list = taglist
325 return self._choice_list
327 def parse_from_arg(self, s):
328 if self.strict and s not in self._choice_list:
329 raise ValidationError("'%s' does not belong to list of available choices '%s'" % (s, self._choice_list))
333 c['schedulers'].append(SingleBranchScheduler(
335 change_filter = filter.ChangeFilter(branch=repo_branch),
336 treeStableTimer = 60,
337 builderNames = list(targets)))
339 c['schedulers'].append(ForceScheduler(
341 buttonName = "Force builds",
342 label = "Force build details",
343 builderNames = [ "00_force_build" ],
346 util.CodebaseParameter(
348 label = "Repository",
349 branch = util.FixedParameter(name = "branch", default = ""),
350 revision = util.FixedParameter(name = "revision", default = ""),
351 repository = util.FixedParameter(name = "repository", default = ""),
352 project = util.FixedParameter(name = "project", default = "")
356 reason = util.StringParameter(
359 default = "Trigger build",
365 util.NestedParameter(
367 label="Build Options",
370 util.ChoiceStringParameter(
372 label = "Build target",
374 choices = set( "all" ) | targets
388 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
389 # what steps, and which workers can execute them. Note that any particular build will
390 # only take place on one worker.
392 def IsTaggingRequested(step):
393 tag = step.getProperty("tag")
394 return tag and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", tag)
396 def IsNoMasterBuild(step):
397 return step.getProperty("branch") != "master"
399 def IsUsignEnabled(step):
400 branch = step.getProperty("branch")
401 return branch and branches[branch].get("usign_key")
403 def IsSignEnabled(step):
404 branch = step.getProperty("branch")
405 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
407 def IsKmodArchiveEnabled(step):
408 branch = step.getProperty("branch")
409 return branch and branches[branch].get("kmod_archive")
411 def GetBaseVersion(branch):
412 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
413 return branch.split('-')[1]
418 def GetVersionPrefix(props):
419 branch = props.getProperty("branch")
420 basever = GetBaseVersion(branch)
421 if props.hasProperty("tag") and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]):
422 return "%s/" % props["tag"]
423 elif basever != "master":
424 return "%s-SNAPSHOT/" % basever
429 def GetConfigSeed(props):
430 branch = props.getProperty("branch")
431 return branch and branches[branch].get("config_seed") or ""
434 def GetRsyncParams(props, srcorbin, urlorkey):
435 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
436 branch = props.getProperty("branch")
437 opt = srcorbin + "_" + urlorkey
438 return branch and branches[branch].get(opt)
441 def GetUsignKey(props):
442 branch = props.getProperty("branch")
443 return branch and branches[branch].get("usign_key")
445 def GetNextBuild(builder, requests):
448 # order tagged build first
449 if r.properties.hasProperty("tag"):
451 # then order by branch order
452 pbranch = r.properties.getProperty("branch")
453 for name in branchNames:
458 log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
461 def MakeEnv(overrides=None, tryccache=False):
463 'CCC': Interpolate("%(prop:cc_command:-gcc)s"),
464 'CCXX': Interpolate("%(prop:cxx_command:-g++)s"),
467 env['CC'] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
468 env['CXX'] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
469 env['CCACHE'] = Interpolate("%(prop:ccache_command:-)s")
471 env['CC'] = env['CCC']
472 env['CXX'] = env['CCXX']
474 if overrides is not None:
475 env.update(overrides)
479 def NetLockDl(props):
481 if props.hasProperty("dl_lock"):
482 lock = NetLocks[props["dl_lock"]]
484 return [lock.access('exclusive')]
489 def NetLockUl(props):
491 if props.hasProperty("ul_lock"):
492 lock = NetLocks[props["ul_lock"]]
494 return [lock.access('exclusive')]
499 def TagPropertyValue(props):
500 if props.hasProperty("options"):
501 options = props.getProperty("options")
502 if type(options) is dict:
503 return options.get("tag")
506 def IsTargetSelected(target):
507 def CheckTargetProperty(step):
509 options = step.getProperty("options")
510 if type(options) is dict:
511 selected_target = options.get("target", "all")
512 if selected_target != "all" and selected_target != target:
519 return CheckTargetProperty
522 def UsignSec2Pub(props):
523 branch = props.getProperty("branch")
525 comment = branches[branch].get("usign_comment") or "untrusted comment: secret key"
526 seckey = branches[branch].get("usign_key")
527 seckey = base64.b64decode(seckey)
531 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
532 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
537 dlLock = locks.WorkerLock("worker_dl")
541 for worker in c['workers']:
542 workerNames.append(worker.workername)
544 force_factory = BuildFactory()
546 c['builders'].append(BuilderConfig(
547 name = "00_force_build",
548 workernames = workerNames,
549 factory = force_factory))
551 for target in targets:
552 ts = target.split('/')
554 factory = BuildFactory()
556 # setup shared work directory if required
557 factory.addStep(ShellCommand(
559 description = "Setting up shared work directory",
560 command = 'test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
562 haltOnFailure = True))
564 # find number of cores
565 factory.addStep(SetPropertyFromCommand(
568 description = "Finding number of CPUs",
569 command = ["nproc"]))
571 # find gcc and g++ compilers
572 factory.addStep(FileDownload(
573 name = "dlfindbinpl",
574 mastersrc = scripts_dir + '/findbin.pl',
575 workerdest = "../findbin.pl",
578 factory.addStep(SetPropertyFromCommand(
580 property = "cc_command",
581 description = "Finding gcc command",
583 "../findbin.pl", "gcc", "", "",
585 haltOnFailure = True))
587 factory.addStep(SetPropertyFromCommand(
589 property = "cxx_command",
590 description = "Finding g++ command",
592 "../findbin.pl", "g++", "", "",
594 haltOnFailure = True))
596 # see if ccache is available
597 factory.addStep(SetPropertyFromCommand(
598 property = "ccache_command",
599 command = ["which", "ccache"],
600 description = "Testing for ccache command",
601 haltOnFailure = False,
602 flunkOnFailure = False,
603 warnOnFailure = False,
606 # Workaround bug when switching from a checked out tag back to a branch
607 # Ref: http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
608 factory.addStep(ShellCommand(
609 name = "gitcheckout",
610 description = "Ensure that Git HEAD is sane",
611 command = Interpolate("if [ -d .git ]; then git checkout -f %(prop:branch)s && git branch --set-upstream-to origin/%(prop:branch)s || rm -fr .git; else exit 0; fi"),
612 haltOnFailure = True))
614 # check out the source
616 # if repo doesn't exist: 'git clone repourl'
617 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -d -f x'. Only works with mode='full'
618 # 'git fetch -t repourl branch; git reset --hard revision'
625 haltOnFailure = True,
629 factory.addStep(ShellCommand(
631 description = "Fetching Git remote refs",
632 command = ["git", "fetch", "origin", Interpolate("+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s")],
637 factory.addStep(ShellCommand(
639 description = "Checking out Git tag",
640 command = ["git", "checkout", Interpolate("tags/v%(prop:tag:-)s")],
641 haltOnFailure = True,
642 doStepIf = IsTaggingRequested
645 # Verify that Git HEAD points to a tag or branch
646 # Ref: http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
647 factory.addStep(ShellCommand(
649 description = "Ensure that Git HEAD is pointing to a branch or tag",
650 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]\\."',
651 haltOnFailure = True))
653 factory.addStep(ShellCommand(
655 description = "Remove tmp folder",
656 command=["rm", "-rf", "tmp/"]))
659 factory.addStep(ShellCommand(
660 name = "rmfeedlinks",
661 description = "Remove feed symlinks",
662 command=["rm", "-rf", "package/feeds/"]))
664 factory.addStep(StringDownload(
666 s = '#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
667 workerdest = "../ccache_cc.sh",
671 factory.addStep(StringDownload(
673 s = '#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
674 workerdest = "../ccache_cxx.sh",
679 factory.addStep(ShellCommand(
680 name = "updatefeeds",
681 description = "Updating feeds",
682 command=["./scripts/feeds", "update"],
683 env = MakeEnv(tryccache=True),
684 haltOnFailure = True,
689 factory.addStep(ShellCommand(
690 name = "installfeeds",
691 description = "Installing feeds",
692 command=["./scripts/feeds", "install", "-a"],
693 env = MakeEnv(tryccache=True),
698 factory.addStep(StringDownload(
699 name = "dlconfigseed",
700 s = Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
701 workerdest = ".config",
706 factory.addStep(ShellCommand(
708 description = "Seeding .config",
709 command = Interpolate("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", target=ts[0], subtarget=ts[1], usign=GetUsignKey)
712 factory.addStep(ShellCommand(
714 description = "Removing output directory",
715 command = ["rm", "-rf", "bin/"]
718 factory.addStep(ShellCommand(
720 description = "Populating .config",
721 command = ["make", "defconfig"],
725 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
726 factory.addStep(ShellCommand(
728 description = "Checking architecture",
729 command = 'grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config' %(ts[0], ts[1]),
733 haltOnFailure = True,
734 flunkOnFailure = False, # this is not a build FAILURE
738 factory.addStep(SetPropertyFromCommand(
741 description = "Finding libc suffix",
742 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
745 factory.addStep(StringDownload(
746 name = "dlkeybuildpub",
747 s = Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
748 workerdest = "key-build.pub",
750 doStepIf = IsUsignEnabled,
753 factory.addStep(StringDownload(
755 s = "# fake private key",
756 workerdest = "key-build",
758 doStepIf = IsUsignEnabled,
761 factory.addStep(StringDownload(
762 name = "dlkeybuilducert",
763 s = "# fake certificate",
764 workerdest = "key-build.ucert",
766 doStepIf = IsUsignEnabled,
770 factory.addStep(ShellCommand(
772 description = "Preparing dl/",
773 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
779 factory.addStep(ShellCommand(
781 description = "Building and installing GNU tar",
782 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/tar/compile", "V=s"],
783 env = MakeEnv(tryccache=True),
788 factory.addStep(ShellCommand(
790 description = "Populating dl/",
791 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
794 locks = properties.FlattenList(NetLockDl, [dlLock.access('exclusive')]),
797 factory.addStep(ShellCommand(
799 description = "Cleaning base-files",
800 command=["make", "package/base-files/clean", "V=s"]
804 factory.addStep(ShellCommand(
806 description = "Building and installing tools",
807 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/install", "V=s"],
808 env = MakeEnv(tryccache=True),
812 factory.addStep(ShellCommand(
814 description = "Building and installing toolchain",
815 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "toolchain/install", "V=s"],
820 factory.addStep(ShellCommand(
822 description = "Building kmods",
823 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
828 # find kernel version
829 factory.addStep(SetPropertyFromCommand(
830 name = "kernelversion",
831 property = "kernelversion",
832 description = "Finding the effective Kernel version",
833 command = "make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
834 env = { 'TOPDIR': Interpolate("%(prop:builddir)s/build") }
837 factory.addStep(ShellCommand(
839 description = "Cleaning up package build",
840 command=["make", "package/cleanup", "V=s"]
843 factory.addStep(ShellCommand(
845 description = "Building packages",
846 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
851 factory.addStep(ShellCommand(
853 description = "Installing packages",
854 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/install", "V=s"],
859 factory.addStep(ShellCommand(
861 description = "Indexing packages",
862 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES="],
867 factory.addStep(ShellCommand(
869 description = "Building and installing images",
870 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/install", "V=s"],
875 factory.addStep(ShellCommand(
877 description = "Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
878 command = "make -j1 buildinfo V=s || true",
883 factory.addStep(ShellCommand(
884 name = "json_overview_image_info",
885 description = "Generate profiles.json in target folder",
886 command = "make -j1 json_overview_image_info V=s || true",
891 factory.addStep(ShellCommand(
893 description = "Calculating checksums",
894 command=["make", "-j1", "checksum", "V=s"],
899 factory.addStep(ShellCommand(
901 description = "Creating kmod directory",
902 command=["mkdir", "-p", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s", target=ts[0], subtarget=ts[1])],
903 haltOnFailure = True,
904 doStepIf = IsKmodArchiveEnabled,
907 factory.addStep(ShellCommand(
908 name = "kmodprepare",
909 description = "Preparing kmod archive",
910 command=["rsync", "--include=/kmod-*.ipk", "--exclude=*", "-va",
911 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/", target=ts[0], subtarget=ts[1]),
912 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
913 haltOnFailure = True,
914 doStepIf = IsKmodArchiveEnabled,
917 factory.addStep(ShellCommand(
919 description = "Indexing kmod archive",
920 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES=",
921 Interpolate("PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
923 haltOnFailure = True,
924 doStepIf = IsKmodArchiveEnabled,
928 factory.addStep(MasterShellCommand(
929 name = "signprepare",
930 description = "Preparing temporary signing directory",
931 command = ["mkdir", "-p", "%s/signing" %(work_dir)],
932 haltOnFailure = True,
933 doStepIf = IsSignEnabled,
937 factory.addStep(ShellCommand(
939 description = "Packing files to sign",
940 command = Interpolate("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", target=ts[0], subtarget=ts[1]),
941 haltOnFailure = True,
942 doStepIf = IsSignEnabled,
945 factory.addStep(FileUpload(
946 workersrc = "sign.tar.gz",
947 masterdest = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
948 haltOnFailure = True,
949 doStepIf = IsSignEnabled,
952 factory.addStep(MasterShellCommand(
954 description = "Signing files",
955 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1])],
956 env = { 'CONFIG_INI': os.getenv("BUILDMASTER_CONFIG", "./config.ini") },
957 haltOnFailure = True,
958 doStepIf = IsSignEnabled,
961 factory.addStep(FileDownload(
962 name = "dlsigntargz",
963 mastersrc = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
964 workerdest = "sign.tar.gz",
965 haltOnFailure = True,
966 doStepIf = IsSignEnabled,
969 factory.addStep(ShellCommand(
971 description = "Unpacking signed files",
972 command = ["tar", "-xzf", "sign.tar.gz"],
973 haltOnFailure = True,
974 doStepIf = IsSignEnabled,
978 factory.addStep(ShellCommand(
980 description = "Preparing upload directory structure",
981 command = ["mkdir", "-p", Interpolate("tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s", target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
985 factory.addStep(ShellCommand(
986 name = "linkprepare",
987 description = "Preparing repository symlink",
988 command = ["ln", "-s", "-f", Interpolate("../packages-%(kw:basever)s", basever=util.Transform(GetBaseVersion, Property("branch"))), Interpolate("tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix)],
989 doStepIf = IsNoMasterBuild,
993 factory.addStep(ShellCommand(
994 name = "kmoddirprepare",
995 description = "Preparing kmod archive upload directory",
996 command = ["mkdir", "-p", Interpolate("tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s", target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
997 haltOnFailure = True,
998 doStepIf = IsKmodArchiveEnabled,
1001 factory.addStep(ShellCommand(
1003 description = "Uploading directory structure",
1004 command = ["rsync", "-az"] + rsync_defopts + ["tmp/upload/", Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url"))],
1005 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1006 haltOnFailure = True,
1011 # download remote sha256sums to 'target-sha256sums'
1012 factory.addStep(ShellCommand(
1013 name = "target-sha256sums",
1014 description = "Fetching remote sha256sums for target",
1015 command = ["rsync", "-z"] + rsync_defopts + [Interpolate("%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/sha256sums", url=GetRsyncParams.withArgs("bin", "url"), target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix), "target-sha256sums"],
1016 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1018 haltOnFailure = False,
1019 flunkOnFailure = False,
1020 warnOnFailure = False,
1023 # build list of files to upload
1024 factory.addStep(FileDownload(
1025 name = "dlsha2rsyncpl",
1026 mastersrc = scripts_dir + '/sha2rsync.pl',
1027 workerdest = "../sha2rsync.pl",
1031 factory.addStep(ShellCommand(
1033 description = "Building list of files to upload",
1034 command = ["../sha2rsync.pl", "target-sha256sums", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/sha256sums", target=ts[0], subtarget=ts[1]), "rsynclist"],
1035 haltOnFailure = True,
1038 factory.addStep(FileDownload(
1039 name = "dlrsync.sh",
1040 mastersrc = scripts_dir + '/rsync.sh',
1041 workerdest = "../rsync.sh",
1045 # upload new files and update existing ones
1046 factory.addStep(ShellCommand(
1047 name = "targetupload",
1048 description = "Uploading target files",
1049 command=["../rsync.sh", "--exclude=/kmods/", "--files-from=rsynclist", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1050 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1051 Interpolate("%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/", url=GetRsyncParams.withArgs("bin", "url"), target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
1052 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1053 haltOnFailure = True,
1057 # delete files which don't exist locally
1058 factory.addStep(ShellCommand(
1059 name = "targetprune",
1060 description = "Pruning target files",
1061 command=["../rsync.sh", "--exclude=/kmods/", "--delete", "--existing", "--ignore-existing", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1062 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1063 Interpolate("%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/", url=GetRsyncParams.withArgs("bin", "url"), target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
1064 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1065 haltOnFailure = True,
1070 factory.addStep(ShellCommand(
1071 name = "kmodupload",
1072 description = "Uploading kmod archive",
1073 command=["../rsync.sh", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1074 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1]),
1075 Interpolate("%(kw:url)s/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s/kmods/%(prop:kernelversion)s/", url=GetRsyncParams.withArgs("bin", "url"), target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
1076 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1077 haltOnFailure = True,
1080 doStepIf = IsKmodArchiveEnabled,
1083 factory.addStep(ShellCommand(
1084 name = "sourcelist",
1085 description = "Finding source archives to upload",
1086 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1087 haltOnFailure = True
1090 factory.addStep(ShellCommand(
1091 name = "sourceupload",
1092 description = "Uploading source archives",
1093 command=["../rsync.sh", "--files-from=sourcelist", "--size-only", "--delay-updates"] + rsync_defopts +
1094 [Interpolate("--partial-dir=.~tmp~%(kw:target)s~%(kw:subtarget)s~%(prop:workername)s", target=ts[0], subtarget=ts[1]), "-a", "dl/", Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("src", "url"))],
1095 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")) },
1096 haltOnFailure = True,
1101 factory.addStep(ShellCommand(
1103 description = "Reporting disk usage",
1104 command=["df", "-h", "."],
1105 env={'LC_ALL': 'C'},
1106 haltOnFailure = False,
1107 flunkOnFailure = False,
1108 warnOnFailure = False,
1112 factory.addStep(ShellCommand(
1114 description = "Reporting estimated file space usage",
1115 command=["du", "-sh", "."],
1116 env={'LC_ALL': 'C'},
1117 haltOnFailure = False,
1118 flunkOnFailure = False,
1119 warnOnFailure = False,
1123 factory.addStep(ShellCommand(
1124 name = "ccachestat",
1125 description = "Reporting ccache stats",
1126 command=["ccache", "-s"],
1127 env = MakeEnv(overrides={ 'PATH': ["${PATH}", "./staging_dir/host/bin"] }),
1128 want_stderr = False,
1129 haltOnFailure = False,
1130 flunkOnFailure = False,
1131 warnOnFailure = False,
1135 c['builders'].append(BuilderConfig(name=target, workernames=workerNames, factory=factory, nextBuild=GetNextBuild))
1137 c['schedulers'].append(schedulers.Triggerable(name="trigger_%s" % target, builderNames=[ target ]))
1138 force_factory.addStep(steps.Trigger(
1139 name = "trigger_%s" % target,
1140 description = "Triggering %s build" % target,
1141 schedulerNames = [ "trigger_%s" % target ],
1142 set_properties = { "reason": Property("reason"), "tag": TagPropertyValue },
1143 doStepIf = IsTargetSelected(target)
1147 ####### STATUS TARGETS
1149 # 'status' is a list of Status Targets. The results of each build will be
1150 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1151 # including web pages, email senders, and IRC bots.
1153 if "status_bind" in inip1:
1155 'port': inip1.get("status_bind"),
1157 'waterfall_view': True,
1158 'console_view': True,
1163 if "status_user" in inip1 and "status_password" in inip1:
1164 c['www']['auth'] = util.UserPasswordAuth([
1165 (inip1.get("status_user"), inip1.get("status_password"))
1167 c['www']['authz'] = util.Authz(
1168 allowRules=[ util.AnyControlEndpointMatcher(role="admins") ],
1169 roleMatchers=[ util.RolesFromUsername(roles=["admins"], usernames=[inip1.get("status_user")]) ]
1173 if ini.has_section("irc"):
1175 irc_host = iniirc.get("host", None)
1176 irc_port = iniirc.getint("port", 6667)
1177 irc_chan = iniirc.get("channel", None)
1178 irc_nick = iniirc.get("nickname", None)
1179 irc_pass = iniirc.get("password", None)
1181 if irc_host and irc_nick and irc_chan:
1182 irc = reporters.IRC(irc_host, irc_nick,
1184 password = irc_pass,
1185 channels = [ irc_chan ],
1186 notify_events = [ 'exception', 'problem', 'recovery' ]
1189 c['services'].append(irc)
1191 c['revlink'] = util.RevlinkMatch([
1192 r'https://git.openwrt.org/openwrt/(.*).git'
1194 r'https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s')
1199 # This specifies what database buildbot uses to store its state. You can leave
1200 # this at its default for all but the largest installations.
1201 'db_url' : "sqlite:///state.sqlite",
1204 c['buildbotNetUsageData'] = None