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
251 def populateTargets():
252 sourcegit = work_dir + '/source.git'
253 if os.path.isdir(sourcegit):
254 subprocess.call(["rm", "-rf", sourcegit])
256 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, sourcegit])
258 os.makedirs(sourcegit + '/tmp', exist_ok=True)
259 findtargets = subprocess.Popen(['./scripts/dump-target-info.pl', 'targets'],
260 stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, cwd = sourcegit)
263 line = findtargets.stdout.readline()
266 ta = line.decode().strip().split(' ')
267 targets.append(ta[0])
269 subprocess.call(["rm", "-rf", sourcegit])
273 # the 'change_source' setting tells the buildmaster how it should find out
274 # about source code changes. Here we point to the buildbot clone of pyflakes.
276 c['change_source'] = []
277 c['change_source'].append(GitPoller(
279 workdir=work_dir+'/work.git', branch=repo_branch,
284 # Configure the Schedulers, which decide how to react to incoming changes. In this
285 # case, just kick off a 'basebuild' build
287 class TagChoiceParameter(BaseParameter):
288 spec_attributes = ["strict", "choices"]
292 def __init__(self, name, label=None, **kw):
293 super().__init__(name, label, **kw)
294 self._choice_list = []
299 basever = re.search(r'-([0-9]+\.[0-9]+)$', repo_branch)
302 findtags = subprocess.Popen(
303 ['git', 'ls-remote', '--tags', repo_url],
304 stdout = subprocess.PIPE)
307 line = findtags.stdout.readline()
312 tagver = re.search(r'\brefs/tags/v([0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$', line.decode().strip())
314 if tagver and tagver[1].find(basever[1]) == 0:
315 taglist.append(tagver[1])
317 taglist.sort(reverse=True, key=lambda tag: tag if re.search(r'-rc[0-9]+$', tag) else tag + '-z')
318 taglist.insert(0, '')
320 self._choice_list = taglist
322 return self._choice_list
324 def parse_from_arg(self, s):
325 if self.strict and s not in self._choice_list:
326 raise ValidationError("'%s' does not belong to list of available choices '%s'" % (s, self._choice_list))
330 c['schedulers'].append(SingleBranchScheduler(
332 change_filter = filter.ChangeFilter(branch=repo_branch),
333 treeStableTimer = 60,
334 builderNames = targets))
336 c['schedulers'].append(ForceScheduler(
338 buttonName = "Force builds",
339 label = "Force build details",
340 builderNames = [ "00_force_build" ],
343 util.CodebaseParameter(
345 label = "Repository",
346 branch = util.FixedParameter(name = "branch", default = ""),
347 revision = util.FixedParameter(name = "revision", default = ""),
348 repository = util.FixedParameter(name = "repository", default = ""),
349 project = util.FixedParameter(name = "project", default = "")
353 reason = util.StringParameter(
356 default = "Trigger build",
362 util.NestedParameter(
364 label="Build Options",
367 util.ChoiceStringParameter(
369 label = "Build target",
371 choices = [ "all" ] + targets
385 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
386 # what steps, and which workers can execute them. Note that any particular build will
387 # only take place on one worker.
389 def IsTaggingRequested(step):
390 tag = step.getProperty("tag")
391 return tag and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", tag)
393 def IsNoMasterBuild(step):
394 return step.getProperty("branch") != "master"
396 def IsUsignEnabled(step):
397 branch = step.getProperty("branch")
398 return branch and branches[branch].get("usign_key")
400 def IsSignEnabled(step):
401 branch = step.getProperty("branch")
402 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
404 def IsKmodArchiveEnabled(step):
405 branch = step.getProperty("branch")
406 return branch and branches[branch].get("kmod_archive")
408 def GetBaseVersion(branch):
409 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
410 return branch.split('-')[1]
415 def GetVersionPrefix(props):
416 branch = props.getProperty("branch")
417 basever = GetBaseVersion(branch)
418 if props.hasProperty("tag") and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]):
419 return "%s/" % props["tag"]
420 elif basever != "master":
421 return "%s-SNAPSHOT/" % basever
426 def GetConfigSeed(props):
427 branch = props.getProperty("branch")
428 return branch and branches[branch].get("config_seed") or ""
431 def GetRsyncParams(props, srcorbin, urlorkey):
432 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
433 branch = props.getProperty("branch")
434 opt = srcorbin + "_" + urlorkey
435 return branch and branches[branch].get(opt)
438 def GetUsignKey(props):
439 branch = props.getProperty("branch")
440 return branch and branches[branch].get("usign_key")
442 def GetNextBuild(builder, requests):
444 if r.properties and r.properties.hasProperty("tag"):
448 log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
451 def MakeEnv(overrides=None, tryccache=False):
453 'CCC': Interpolate("%(prop:cc_command:-gcc)s"),
454 'CCXX': Interpolate("%(prop:cxx_command:-g++)s"),
457 env['CC'] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
458 env['CXX'] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
459 env['CCACHE'] = Interpolate("%(prop:ccache_command:-)s")
461 env['CC'] = env['CCC']
462 env['CXX'] = env['CCXX']
464 if overrides is not None:
465 env.update(overrides)
469 def NetLockDl(props):
471 if props.hasProperty("dl_lock"):
472 lock = NetLocks[props["dl_lock"]]
474 return [lock.access('exclusive')]
479 def NetLockUl(props):
481 if props.hasProperty("ul_lock"):
482 lock = NetLocks[props["ul_lock"]]
484 return [lock.access('exclusive')]
489 def TagPropertyValue(props):
490 if props.hasProperty("options"):
491 options = props.getProperty("options")
492 if type(options) is dict:
493 return options.get("tag")
496 def IsTargetSelected(target):
497 def CheckTargetProperty(step):
499 options = step.getProperty("options")
500 if type(options) is dict:
501 selected_target = options.get("target", "all")
502 if selected_target != "all" and selected_target != target:
509 return CheckTargetProperty
512 def UsignSec2Pub(props):
513 branch = props.getProperty("branch")
515 comment = branches[branch].get("usign_comment") or "untrusted comment: secret key"
516 seckey = branches[branch].get("usign_key")
517 seckey = base64.b64decode(seckey)
521 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
522 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
527 dlLock = locks.WorkerLock("worker_dl")
531 for worker in c['workers']:
532 workerNames.append(worker.workername)
534 force_factory = BuildFactory()
536 c['builders'].append(BuilderConfig(
537 name = "00_force_build",
538 workernames = workerNames,
539 factory = force_factory))
541 for target in targets:
542 ts = target.split('/')
544 factory = BuildFactory()
546 # setup shared work directory if required
547 factory.addStep(ShellCommand(
549 description = "Setting up shared work directory",
550 command = 'test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
552 haltOnFailure = True))
554 # find number of cores
555 factory.addStep(SetPropertyFromCommand(
558 description = "Finding number of CPUs",
559 command = ["nproc"]))
561 # find gcc and g++ compilers
562 factory.addStep(FileDownload(
563 name = "dlfindbinpl",
564 mastersrc = scripts_dir + '/findbin.pl',
565 workerdest = "../findbin.pl",
568 factory.addStep(SetPropertyFromCommand(
570 property = "cc_command",
571 description = "Finding gcc command",
573 "../findbin.pl", "gcc", "", "",
575 haltOnFailure = True))
577 factory.addStep(SetPropertyFromCommand(
579 property = "cxx_command",
580 description = "Finding g++ command",
582 "../findbin.pl", "g++", "", "",
584 haltOnFailure = True))
586 # see if ccache is available
587 factory.addStep(SetPropertyFromCommand(
588 property = "ccache_command",
589 command = ["which", "ccache"],
590 description = "Testing for ccache command",
591 haltOnFailure = False,
592 flunkOnFailure = False,
593 warnOnFailure = False,
596 # Workaround bug when switching from a checked out tag back to a branch
597 # Ref: http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
598 factory.addStep(ShellCommand(
599 name = "gitcheckout",
600 description = "Ensure that Git HEAD is sane",
601 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"),
602 haltOnFailure = True))
604 # check out the source
606 # if repo doesn't exist: 'git clone repourl'
607 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -d -f x'. Only works with mode='full'
608 # 'git fetch -t repourl branch; git reset --hard revision'
615 haltOnFailure = True,
619 factory.addStep(ShellCommand(
621 description = "Fetching Git remote refs",
622 command = ["git", "fetch", "origin", Interpolate("+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s")],
627 factory.addStep(ShellCommand(
629 description = "Checking out Git tag",
630 command = ["git", "checkout", Interpolate("tags/v%(prop:tag:-)s")],
631 haltOnFailure = True,
632 doStepIf = IsTaggingRequested
635 # Verify that Git HEAD points to a tag or branch
636 # Ref: http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
637 factory.addStep(ShellCommand(
639 description = "Ensure that Git HEAD is pointing to a branch or tag",
640 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]\\."',
641 haltOnFailure = True))
643 factory.addStep(ShellCommand(
645 description = "Remove tmp folder",
646 command=["rm", "-rf", "tmp/"]))
649 factory.addStep(ShellCommand(
650 name = "rmfeedlinks",
651 description = "Remove feed symlinks",
652 command=["rm", "-rf", "package/feeds/"]))
654 factory.addStep(StringDownload(
656 s = '#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
657 workerdest = "../ccache_cc.sh",
661 factory.addStep(StringDownload(
663 s = '#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
664 workerdest = "../ccache_cxx.sh",
669 factory.addStep(ShellCommand(
670 name = "updatefeeds",
671 description = "Updating feeds",
672 command=["./scripts/feeds", "update"],
673 env = MakeEnv(tryccache=True),
674 haltOnFailure = True,
679 factory.addStep(ShellCommand(
680 name = "installfeeds",
681 description = "Installing feeds",
682 command=["./scripts/feeds", "install", "-a"],
683 env = MakeEnv(tryccache=True),
688 factory.addStep(StringDownload(
689 name = "dlconfigseed",
690 s = Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
691 workerdest = ".config",
696 factory.addStep(ShellCommand(
698 description = "Seeding .config",
699 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)
702 factory.addStep(ShellCommand(
704 description = "Removing output directory",
705 command = ["rm", "-rf", "bin/"]
708 factory.addStep(ShellCommand(
710 description = "Populating .config",
711 command = ["make", "defconfig"],
716 factory.addStep(ShellCommand(
718 description = "Checking architecture",
719 command = ["grep", "-sq", "CONFIG_TARGET_%s=y" %(ts[0]), ".config"],
727 factory.addStep(SetPropertyFromCommand(
730 description = "Finding libc suffix",
731 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
734 factory.addStep(StringDownload(
735 name = "dlkeybuildpub",
736 s = Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
737 workerdest = "key-build.pub",
739 doStepIf = IsUsignEnabled,
742 factory.addStep(StringDownload(
744 s = "# fake private key",
745 workerdest = "key-build",
747 doStepIf = IsUsignEnabled,
750 factory.addStep(StringDownload(
751 name = "dlkeybuilducert",
752 s = "# fake certificate",
753 workerdest = "key-build.ucert",
755 doStepIf = IsUsignEnabled,
759 factory.addStep(ShellCommand(
761 description = "Preparing dl/",
762 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
768 factory.addStep(ShellCommand(
770 description = "Building and installing GNU tar",
771 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/tar/compile", "V=s"],
772 env = MakeEnv(tryccache=True),
777 factory.addStep(ShellCommand(
779 description = "Populating dl/",
780 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
783 locks = properties.FlattenList(NetLockDl, [dlLock.access('exclusive')]),
786 factory.addStep(ShellCommand(
788 description = "Cleaning base-files",
789 command=["make", "package/base-files/clean", "V=s"]
793 factory.addStep(ShellCommand(
795 description = "Building and installing tools",
796 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/install", "V=s"],
797 env = MakeEnv(tryccache=True),
801 factory.addStep(ShellCommand(
803 description = "Building and installing toolchain",
804 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "toolchain/install", "V=s"],
809 factory.addStep(ShellCommand(
811 description = "Building kmods",
812 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
817 # find kernel version
818 factory.addStep(SetPropertyFromCommand(
819 name = "kernelversion",
820 property = "kernelversion",
821 description = "Finding the effective Kernel version",
822 command = "make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
823 env = { 'TOPDIR': Interpolate("%(prop:builddir)s/build") }
826 factory.addStep(ShellCommand(
828 description = "Cleaning up package build",
829 command=["make", "package/cleanup", "V=s"]
832 factory.addStep(ShellCommand(
834 description = "Building packages",
835 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
840 factory.addStep(ShellCommand(
842 description = "Installing packages",
843 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/install", "V=s"],
848 factory.addStep(ShellCommand(
850 description = "Indexing packages",
851 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES="],
856 factory.addStep(ShellCommand(
858 description = "Building and installing images",
859 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/install", "V=s"],
864 factory.addStep(ShellCommand(
866 description = "Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
867 command = "make -j1 buildinfo V=s || true",
872 factory.addStep(ShellCommand(
873 name = "json_overview_image_info",
874 description = "Generate profiles.json in target folder",
875 command = "make -j1 json_overview_image_info V=s || true",
880 factory.addStep(ShellCommand(
882 description = "Calculating checksums",
883 command=["make", "-j1", "checksum", "V=s"],
888 factory.addStep(ShellCommand(
890 description = "Creating kmod directory",
891 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])],
892 haltOnFailure = True,
893 doStepIf = IsKmodArchiveEnabled,
896 factory.addStep(ShellCommand(
897 name = "kmodprepare",
898 description = "Preparing kmod archive",
899 command=["rsync", "--include=/kmod-*.ipk", "--exclude=*", "-va",
900 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/", target=ts[0], subtarget=ts[1]),
901 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
902 haltOnFailure = True,
903 doStepIf = IsKmodArchiveEnabled,
906 factory.addStep(ShellCommand(
908 description = "Indexing kmod archive",
909 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES=",
910 Interpolate("PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
912 haltOnFailure = True,
913 doStepIf = IsKmodArchiveEnabled,
917 factory.addStep(MasterShellCommand(
918 name = "signprepare",
919 description = "Preparing temporary signing directory",
920 command = ["mkdir", "-p", "%s/signing" %(work_dir)],
921 haltOnFailure = True,
922 doStepIf = IsSignEnabled,
926 factory.addStep(ShellCommand(
928 description = "Packing files to sign",
929 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]),
930 haltOnFailure = True,
931 doStepIf = IsSignEnabled,
934 factory.addStep(FileUpload(
935 workersrc = "sign.tar.gz",
936 masterdest = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
937 haltOnFailure = True,
938 doStepIf = IsSignEnabled,
941 factory.addStep(MasterShellCommand(
943 description = "Signing files",
944 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1])],
945 env = { 'CONFIG_INI': os.getenv("BUILDMASTER_CONFIG", "./config.ini") },
946 haltOnFailure = True,
947 doStepIf = IsSignEnabled,
950 factory.addStep(FileDownload(
951 name = "dlsigntargz",
952 mastersrc = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
953 workerdest = "sign.tar.gz",
954 haltOnFailure = True,
955 doStepIf = IsSignEnabled,
958 factory.addStep(ShellCommand(
960 description = "Unpacking signed files",
961 command = ["tar", "-xzf", "sign.tar.gz"],
962 haltOnFailure = True,
963 doStepIf = IsSignEnabled,
967 factory.addStep(ShellCommand(
969 description = "Preparing upload directory structure",
970 command = ["mkdir", "-p", Interpolate("tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s", target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
974 factory.addStep(ShellCommand(
975 name = "linkprepare",
976 description = "Preparing repository symlink",
977 command = ["ln", "-s", "-f", Interpolate("../packages-%(kw:basever)s", basever=util.Transform(GetBaseVersion, Property("branch"))), Interpolate("tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix)],
978 doStepIf = IsNoMasterBuild,
982 factory.addStep(ShellCommand(
983 name = "kmoddirprepare",
984 description = "Preparing kmod archive upload directory",
985 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)],
986 haltOnFailure = True,
987 doStepIf = IsKmodArchiveEnabled,
990 factory.addStep(ShellCommand(
992 description = "Uploading directory structure",
993 command = ["rsync", "-az"] + rsync_defopts + ["tmp/upload/", Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url"))],
994 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
995 haltOnFailure = True,
1000 # download remote sha256sums to 'target-sha256sums'
1001 factory.addStep(ShellCommand(
1002 name = "target-sha256sums",
1003 description = "Fetching remote sha256sums for target",
1004 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"],
1005 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1007 haltOnFailure = False,
1008 flunkOnFailure = False,
1009 warnOnFailure = False,
1012 # build list of files to upload
1013 factory.addStep(FileDownload(
1014 name = "dlsha2rsyncpl",
1015 mastersrc = scripts_dir + '/sha2rsync.pl',
1016 workerdest = "../sha2rsync.pl",
1020 factory.addStep(ShellCommand(
1022 description = "Building list of files to upload",
1023 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"],
1024 haltOnFailure = True,
1027 factory.addStep(FileDownload(
1028 name = "dlrsync.sh",
1029 mastersrc = scripts_dir + '/rsync.sh',
1030 workerdest = "../rsync.sh",
1034 # upload new files and update existing ones
1035 factory.addStep(ShellCommand(
1036 name = "targetupload",
1037 description = "Uploading target files",
1038 command=["../rsync.sh", "--exclude=/kmods/", "--files-from=rsynclist", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1039 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1040 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)],
1041 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1042 haltOnFailure = True,
1046 # delete files which don't exist locally
1047 factory.addStep(ShellCommand(
1048 name = "targetprune",
1049 description = "Pruning target files",
1050 command=["../rsync.sh", "--exclude=/kmods/", "--delete", "--existing", "--ignore-existing", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1051 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1052 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)],
1053 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1054 haltOnFailure = True,
1059 factory.addStep(ShellCommand(
1060 name = "kmodupload",
1061 description = "Uploading kmod archive",
1062 command=["../rsync.sh", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1063 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1]),
1064 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)],
1065 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1066 haltOnFailure = True,
1069 doStepIf = IsKmodArchiveEnabled,
1072 factory.addStep(ShellCommand(
1073 name = "sourcelist",
1074 description = "Finding source archives to upload",
1075 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1076 haltOnFailure = True
1079 factory.addStep(ShellCommand(
1080 name = "sourceupload",
1081 description = "Uploading source archives",
1082 command=["../rsync.sh", "--files-from=sourcelist", "--size-only", "--delay-updates"] + rsync_defopts +
1083 [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"))],
1084 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")) },
1085 haltOnFailure = True,
1090 factory.addStep(ShellCommand(
1092 description = "Reporting disk usage",
1093 command=["df", "-h", "."],
1094 env={'LC_ALL': 'C'},
1095 haltOnFailure = False,
1096 flunkOnFailure = False,
1097 warnOnFailure = False,
1101 factory.addStep(ShellCommand(
1103 description = "Reporting estimated file space usage",
1104 command=["du", "-sh", "."],
1105 env={'LC_ALL': 'C'},
1106 haltOnFailure = False,
1107 flunkOnFailure = False,
1108 warnOnFailure = False,
1112 factory.addStep(ShellCommand(
1113 name = "ccachestat",
1114 description = "Reporting ccache stats",
1115 command=["ccache", "-s"],
1116 env = MakeEnv(overrides={ 'PATH': ["${PATH}", "./staging_dir/host/bin"] }),
1117 want_stderr = False,
1118 haltOnFailure = False,
1119 flunkOnFailure = False,
1120 warnOnFailure = False,
1124 c['builders'].append(BuilderConfig(name=target, workernames=workerNames, factory=factory, nextBuild=GetNextBuild))
1126 c['schedulers'].append(schedulers.Triggerable(name="trigger_%s" % target, builderNames=[ target ]))
1127 force_factory.addStep(steps.Trigger(
1128 name = "trigger_%s" % target,
1129 description = "Triggering %s build" % target,
1130 schedulerNames = [ "trigger_%s" % target ],
1131 set_properties = { "reason": Property("reason"), "tag": TagPropertyValue },
1132 doStepIf = IsTargetSelected(target)
1136 ####### STATUS TARGETS
1138 # 'status' is a list of Status Targets. The results of each build will be
1139 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1140 # including web pages, email senders, and IRC bots.
1142 if "status_bind" in inip1:
1144 'port': inip1.get("status_bind"),
1146 'waterfall_view': True,
1147 'console_view': True,
1152 if "status_user" in inip1 and "status_password" in inip1:
1153 c['www']['auth'] = util.UserPasswordAuth([
1154 (inip1.get("status_user"), inip1.get("status_password"))
1156 c['www']['authz'] = util.Authz(
1157 allowRules=[ util.AnyControlEndpointMatcher(role="admins") ],
1158 roleMatchers=[ util.RolesFromUsername(roles=["admins"], usernames=[inip1.get("status_user")]) ]
1162 if ini.has_section("irc"):
1164 irc_host = iniirc.get("host", None)
1165 irc_port = iniirc.getint("port", 6667)
1166 irc_chan = iniirc.get("channel", None)
1167 irc_nick = iniirc.get("nickname", None)
1168 irc_pass = iniirc.get("password", None)
1170 if irc_host and irc_nick and irc_chan:
1171 irc = reporters.IRC(irc_host, irc_nick,
1173 password = irc_pass,
1174 channels = [ irc_chan ],
1175 notify_events = [ 'exception', 'problem', 'recovery' ]
1178 c['services'].append(irc)
1180 c['revlink'] = util.RevlinkMatch([
1181 r'https://git.openwrt.org/openwrt/(.*).git'
1183 r'https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s')
1188 # This specifies what database buildbot uses to store its state. You can leave
1189 # this at its default for all but the largest installations.
1190 'db_url' : "sqlite:///state.sqlite",
1193 c['buildbotNetUsageData'] = None