2 # ex: set syntax=python:
10 from dateutil.tz import tzutc
11 from datetime import datetime, timedelta
13 from twisted.internet import defer
14 from twisted.python import log
16 from buildbot import locks
17 from buildbot.data import resultspec
18 from buildbot.changes.gitpoller import GitPoller
19 from buildbot.config import BuilderConfig
20 from buildbot.plugins import reporters
21 from buildbot.plugins import schedulers
22 from buildbot.plugins import steps
23 from buildbot.plugins import util
24 from buildbot.process import properties
25 from buildbot.process import results
26 from buildbot.process.factory import BuildFactory
27 from buildbot.process.properties import Interpolate
28 from buildbot.process.properties import Property
29 from buildbot.schedulers.basic import AnyBranchScheduler
30 from buildbot.schedulers.forcesched import BaseParameter
31 from buildbot.schedulers.forcesched import ForceScheduler
32 from buildbot.schedulers.forcesched import ValidationError
33 from buildbot.steps.master import MasterShellCommand
34 from buildbot.steps.shell import SetPropertyFromCommand
35 from buildbot.steps.shell import ShellCommand
36 from buildbot.steps.source.git import Git
37 from buildbot.steps.transfer import FileDownload
38 from buildbot.steps.transfer import FileUpload
39 from buildbot.steps.transfer import StringDownload
40 from buildbot.worker import Worker
43 if not os.path.exists("twistd.pid"):
44 with open("twistd.pid", "w") as pidfile:
45 pidfile.write("{}".format(os.getpid()))
47 # This is a sample buildmaster config file. It must be installed as
48 # 'master.cfg' in your buildmaster's base directory.
50 ini = configparser.ConfigParser()
51 ini.read(os.getenv("BUILDMASTER_CONFIG", "./config.ini"))
53 if "general" not in ini or "phase1" not in ini:
54 raise ValueError("Fix your configuration")
59 work_dir = os.path.abspath(ini['general'].get("workdir", "."))
60 scripts_dir = os.path.abspath("../scripts")
62 repo_url = ini['repo'].get("url")
64 rsync_defopts = ["-v", "-4", "--timeout=120"]
66 #if rsync_bin_url.find("::") > 0 or rsync_bin_url.find("rsync://") == 0:
67 # rsync_bin_defopts += ["--contimeout=20"]
71 def ini_parse_branch(section):
73 name = section.get("name")
76 raise ValueError("missing 'name' in " + repr(section))
78 raise ValueError("duplicate branch name in " + repr(section))
81 b["bin_url"] = section.get("binary_url")
82 b["bin_key"] = section.get("binary_password")
84 b["src_url"] = section.get("source_url")
85 b["src_key"] = section.get("source_password")
87 b["gpg_key"] = section.get("gpg_key")
89 b["usign_key"] = section.get("usign_key")
90 usign_comment = "untrusted comment: " + name.replace("-", " ").title() + " key"
91 b["usign_comment"] = section.get("usign_comment", usign_comment)
93 b["config_seed"] = section.get("config_seed")
95 b["kmod_archive"] = section.getboolean("kmod_archive", False)
98 log.msg("Configured branch: {}".format(name))
100 # PB port can be either a numeric port or a connection string
101 pb_port = inip1.get("port") or 9989
103 # This is the dictionary that the buildmaster pays attention to. We also use
104 # a shorter alias to save typing.
105 c = BuildmasterConfig = {}
107 ####### PROJECT IDENTITY
109 # the 'title' string will appear at the top of this buildbot
110 # installation's html.WebStatus home page (linked to the
111 # 'titleURL') and is embedded in the title of the waterfall HTML page.
113 c['title'] = ini['general'].get("title")
114 c['titleURL'] = ini['general'].get("title_url")
116 # the 'buildbotURL' string should point to the location where the buildbot's
117 # internal web server (usually the html.WebStatus page) is visible. This
118 # typically uses the port number set in the Waterfall 'status' entry, but
119 # with an externally-visible host name which the buildbot cannot figure out
122 c['buildbotURL'] = inip1.get("buildbot_url")
126 # The 'workers' list defines the set of recognized buildworkers. Each element is
127 # a Worker object, specifying a unique worker name and password. The same
128 # worker name and password must be configured on the worker.
133 for section in ini.sections():
134 if section.startswith("branch "):
135 ini_parse_branch(ini[section])
137 if section.startswith("worker "):
138 if ini.has_option(section, "name") and ini.has_option(section, "password") and \
139 (not ini.has_option(section, "phase") or ini.getint(section, "phase") == 1):
140 sl_props = { 'dl_lock':None, 'ul_lock':None }
141 name = ini.get(section, "name")
142 password = ini.get(section, "password")
143 if ini.has_option(section, "dl_lock"):
144 lockname = ini.get(section, "dl_lock")
145 sl_props['dl_lock'] = lockname
146 if lockname not in NetLocks:
147 NetLocks[lockname] = locks.MasterLock(lockname)
148 if ini.has_option(section, "ul_lock"):
149 lockname = ini.get(section, "ul_lock")
150 sl_props['ul_lock'] = lockname
151 if lockname not in NetLocks:
152 NetLocks[lockname] = locks.MasterLock(lockname)
153 c['workers'].append(Worker(name, password, max_builds = 1, properties = sl_props))
155 c['protocols'] = {'pb': {'port': pb_port}}
158 c['collapseRequests'] = True
160 # Reduce amount of backlog data
161 c['configurators'] = [util.JanitorConfigurator(
162 logHorizon=timedelta(days=3),
166 @defer.inlineCallbacks
167 def getNewestCompleteTime(bldr):
168 """Returns the complete_at of the latest completed and not SKIPPED
169 build request for this builder, or None if there are no such build
170 requests. We need to filter out SKIPPED requests because we're
171 using collapseRequests=True which is unfortunately marking all
172 previous requests as complete when new buildset is created.
174 @returns: datetime instance or None, via Deferred
177 bldrid = yield bldr.getBuilderId()
178 completed = yield bldr.master.data.get(
179 ('builders', bldrid, 'buildrequests'),
181 resultspec.Filter('complete', 'eq', [True]),
182 resultspec.Filter('results', 'ne', [results.SKIPPED]),
184 order=['-complete_at'], limit=1)
188 complete_at = completed[0]['complete_at']
190 last_build = yield bldr.master.data.get(
193 resultspec.Filter('builderid', 'eq', [bldrid]),
195 order=['-started_at'], limit=1)
197 if last_build and last_build[0]:
198 last_complete_at = last_build[0]['complete_at']
199 if last_complete_at and (last_complete_at > complete_at):
200 return last_complete_at
204 @defer.inlineCallbacks
205 def prioritizeBuilders(master, builders):
206 """Returns sorted list of builders by their last timestamp of completed and
209 @returns: list of sorted builders
212 def is_building(bldr):
213 return bool(bldr.building) or bool(bldr.old_building)
216 d = defer.maybeDeferred(getNewestCompleteTime, bldr)
217 d.addCallback(lambda complete_at: (complete_at, bldr))
221 (complete_at, bldr) = item
225 complete_at = date.replace(tzinfo=tzutc())
227 if is_building(bldr):
229 complete_at = date.replace(tzinfo=tzutc())
231 return (complete_at, bldr.name)
233 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
234 results.sort(key=bldr_sort)
237 log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
239 return [r[1] for r in results]
241 c['prioritizeBuilders'] = prioritizeBuilders
243 ####### CHANGESOURCES
245 branchNames = [branches[b]["name"] for b in branches]
250 def populateTargets():
251 log.msg("Populating targets, this will take time")
252 sourcegit = work_dir + '/source.git'
253 for branch in branchNames:
254 if os.path.isdir(sourcegit):
255 subprocess.call(["rm", "-rf", sourcegit])
257 subprocess.call(["git", "clone", "-q", "--depth=1", "--branch="+branch, repo_url, sourcegit])
259 os.makedirs(sourcegit + '/tmp', exist_ok=True)
260 findtargets = subprocess.Popen(['./scripts/dump-target-info.pl', 'targets'],
261 stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, cwd = sourcegit)
264 line = findtargets.stdout.readline()
267 ta = line.decode().strip().split(' ')
270 subprocess.call(["rm", "-rf", sourcegit])
274 # the 'change_source' setting tells the buildmaster how it should find out
275 # about source code changes. Here we point to the buildbot clone of pyflakes.
277 c['change_source'] = []
278 c['change_source'].append(GitPoller(
280 workdir=work_dir+'/work.git', branches=branchNames,
281 pollAtLaunch=True, pollinterval=300))
285 # Configure the Schedulers, which decide how to react to incoming changes. In this
286 # case, just kick off a 'basebuild' build
288 class TagChoiceParameter(BaseParameter):
289 spec_attributes = ["strict", "choices"]
293 def __init__(self, name, label=None, **kw):
294 super().__init__(name, label, **kw)
295 self._choice_list = []
300 basever = re.search(r'-([0-9]+\.[0-9]+)$', "master") # XXX FIXME
303 findtags = subprocess.Popen(
304 ['git', 'ls-remote', '--tags', repo_url],
305 stdout = subprocess.PIPE)
308 line = findtags.stdout.readline()
313 tagver = re.search(r'\brefs/tags/v([0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$', line.decode().strip())
315 if tagver and tagver[1].find(basever[1]) == 0:
316 taglist.append(tagver[1])
318 taglist.sort(reverse=True, key=lambda tag: tag if re.search(r'-rc[0-9]+$', tag) else tag + '-z')
319 taglist.insert(0, '')
321 self._choice_list = taglist
323 return self._choice_list
325 def parse_from_arg(self, s):
326 if self.strict and s not in self._choice_list:
327 raise ValidationError("'%s' does not belong to list of available choices '%s'" % (s, self._choice_list))
331 c['schedulers'].append(AnyBranchScheduler(
333 change_filter = util.ChangeFilter(branch=branchNames),
334 treeStableTimer = 15*60,
335 builderNames = list(targets)))
337 c['schedulers'].append(ForceScheduler(
339 buttonName = "Force builds",
340 label = "Force build details",
341 builderNames = [ "00_force_build" ],
344 util.CodebaseParameter(
346 label = "Repository",
347 branch = util.FixedParameter(name = "branch", default = ""),
348 revision = util.FixedParameter(name = "revision", default = ""),
349 repository = util.FixedParameter(name = "repository", default = ""),
350 project = util.FixedParameter(name = "project", default = "")
354 reason = util.StringParameter(
357 default = "Trigger build",
363 util.NestedParameter(
365 label="Build Options",
368 util.ChoiceStringParameter(
370 label = "Build target",
372 choices = set( "all" ) | targets
386 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
387 # what steps, and which workers can execute them. Note that any particular build will
388 # only take place on one worker.
390 def IsTaggingRequested(step):
391 tag = step.getProperty("tag")
392 return tag and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", tag)
394 def IsNoMasterBuild(step):
395 return step.getProperty("branch") != "master"
397 def IsUsignEnabled(step):
398 branch = step.getProperty("branch")
399 return branch and branches[branch].get("usign_key")
401 def IsSignEnabled(step):
402 branch = step.getProperty("branch")
403 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
405 def IsKmodArchiveEnabled(step):
406 branch = step.getProperty("branch")
407 return branch and branches[branch].get("kmod_archive")
409 def GetBaseVersion(branch):
410 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
411 return branch.split('-')[1]
416 def GetVersionPrefix(props):
417 branch = props.getProperty("branch")
418 basever = GetBaseVersion(branch)
419 if props.hasProperty("tag") and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]):
420 return "%s/" % props["tag"]
421 elif basever != "master":
422 return "%s-SNAPSHOT/" % basever
427 def GetConfigSeed(props):
428 branch = props.getProperty("branch")
429 return branch and branches[branch].get("config_seed") or ""
432 def GetRsyncParams(props, srcorbin, urlorkey):
433 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
434 branch = props.getProperty("branch")
435 opt = srcorbin + "_" + urlorkey
436 return branch and branches[branch].get(opt)
439 def GetUsignKey(props):
440 branch = props.getProperty("branch")
441 return branch and branches[branch].get("usign_key")
443 def GetNextBuild(builder, requests):
446 # order tagged build first
447 if r.properties.hasProperty("tag"):
449 # then order by branch order
450 pbranch = r.properties.getProperty("branch")
451 for name in branchNames:
456 log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
459 def MakeEnv(overrides=None, tryccache=False):
461 'CCC': Interpolate("%(prop:cc_command:-gcc)s"),
462 'CCXX': Interpolate("%(prop:cxx_command:-g++)s"),
465 env['CC'] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
466 env['CXX'] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
467 env['CCACHE'] = Interpolate("%(prop:ccache_command:-)s")
469 env['CC'] = env['CCC']
470 env['CXX'] = env['CCXX']
472 if overrides is not None:
473 env.update(overrides)
477 def NetLockDl(props, extralock=None):
480 if props.hasProperty("dl_lock"):
481 lock = NetLocks[props["dl_lock"]]
483 locks.append(lock.access('exclusive'))
484 if extralock is not None:
485 locks.append(extralock)
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 descriptionDone = "Shared work directory set up",
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(
599 property = "ccache_command",
600 command = ["which", "ccache"],
601 description = "Testing for ccache command",
602 haltOnFailure = False,
603 flunkOnFailure = False,
604 warnOnFailure = False,
605 hideStepIf = lambda r, s: r==results.FAILURE,
608 # Workaround bug when switching from a checked out tag back to a branch
609 # Ref: http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
610 factory.addStep(ShellCommand(
611 name = "gitcheckout",
612 description = "Ensure that Git HEAD is sane",
613 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"),
614 haltOnFailure = True))
616 # check out the source
618 # if repo doesn't exist: 'git clone repourl'
619 # method 'clean' runs 'git clean -d -f', method fresh runs 'git clean -d -f x'. Only works with mode='full'
620 # 'git fetch -t repourl branch; git reset --hard revision'
627 haltOnFailure = True,
631 factory.addStep(ShellCommand(
633 description = "Fetching Git remote refs",
634 command = ["git", "fetch", "origin", Interpolate("+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s")],
639 factory.addStep(ShellCommand(
641 description = "Checking out Git tag",
642 command = ["git", "checkout", Interpolate("tags/v%(prop:tag:-)s")],
643 haltOnFailure = True,
644 doStepIf = IsTaggingRequested
647 # Verify that Git HEAD points to a tag or branch
648 # Ref: http://lists.infradead.org/pipermail/openwrt-devel/2019-June/017809.html
649 factory.addStep(ShellCommand(
651 description = "Ensure that Git HEAD is pointing to a branch or tag",
652 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]\\."',
653 haltOnFailure = True))
655 factory.addStep(ShellCommand(
657 description = "Remove tmp folder",
658 command=["rm", "-rf", "tmp/"]))
661 factory.addStep(ShellCommand(
662 name = "rmfeedlinks",
663 description = "Remove feed symlinks",
664 command=["rm", "-rf", "package/feeds/"]))
666 factory.addStep(StringDownload(
668 s = '#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
669 workerdest = "../ccache_cc.sh",
673 factory.addStep(StringDownload(
675 s = '#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
676 workerdest = "../ccache_cxx.sh",
681 factory.addStep(ShellCommand(
682 name = "updatefeeds",
683 description = "Updating feeds",
684 command=["./scripts/feeds", "update"],
685 env = MakeEnv(tryccache=True),
686 haltOnFailure = True,
691 factory.addStep(ShellCommand(
692 name = "installfeeds",
693 description = "Installing feeds",
694 command=["./scripts/feeds", "install", "-a"],
695 env = MakeEnv(tryccache=True),
700 factory.addStep(StringDownload(
701 name = "dlconfigseed",
702 s = Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
703 workerdest = ".config",
708 factory.addStep(ShellCommand(
710 descriptionDone = ".config seeded",
711 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)
714 factory.addStep(ShellCommand(
716 description = "Removing output directory",
717 command = ["rm", "-rf", "bin/"]
720 factory.addStep(ShellCommand(
722 description = "Populating .config",
723 command = ["make", "defconfig"],
727 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
728 factory.addStep(ShellCommand(
730 description = "Checking architecture",
731 descriptionDone = "Architecture validated",
732 command = 'grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config' %(ts[0], ts[1]),
736 haltOnFailure = True,
737 flunkOnFailure = False, # this is not a build FAILURE
741 factory.addStep(SetPropertyFromCommand(
744 description = "Finding libc suffix",
745 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
748 factory.addStep(StringDownload(
749 name = "dlkeybuildpub",
750 s = Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
751 workerdest = "key-build.pub",
753 doStepIf = IsUsignEnabled,
756 factory.addStep(StringDownload(
758 s = "# fake private key",
759 workerdest = "key-build",
761 doStepIf = IsUsignEnabled,
764 factory.addStep(StringDownload(
765 name = "dlkeybuilducert",
766 s = "# fake certificate",
767 workerdest = "key-build.ucert",
769 doStepIf = IsUsignEnabled,
773 factory.addStep(ShellCommand(
775 description = "Preparing dl/",
776 descriptionDone = "dl/ prepared",
777 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
783 factory.addStep(ShellCommand(
785 description = "Building and installing GNU tar",
786 descriptionDone = "GNU tar built and installed",
787 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/tar/compile", "V=s"],
788 env = MakeEnv(tryccache=True),
793 factory.addStep(ShellCommand(
795 description = "Populating dl/",
796 descriptionDone = "dl/ populated",
797 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
800 locks = NetLockDl.withArgs(dlLock.access('exclusive')),
803 factory.addStep(ShellCommand(
805 description = "Cleaning base-files",
806 command=["make", "package/base-files/clean", "V=s"]
810 factory.addStep(ShellCommand(
812 description = "Building and installing tools",
813 descriptionDone = "Tools built and installed",
814 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/install", "V=s"],
815 env = MakeEnv(tryccache=True),
819 factory.addStep(ShellCommand(
821 description = "Building and installing toolchain",
822 descriptionDone = "Toolchain built and installed",
823 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "toolchain/install", "V=s"],
828 factory.addStep(ShellCommand(
830 description = "Building kmods",
831 descriptionDone = "Kmods built",
832 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
837 # find kernel version
838 factory.addStep(SetPropertyFromCommand(
839 name = "kernelversion",
840 property = "kernelversion",
841 description = "Finding the effective Kernel version",
842 command = "make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
843 env = { 'TOPDIR': Interpolate("%(prop:builddir)s/build") }
846 factory.addStep(ShellCommand(
848 description = "Cleaning up package build",
849 descriptionDone = "Package build cleaned up",
850 command=["make", "package/cleanup", "V=s"]
853 factory.addStep(ShellCommand(
855 description = "Building packages",
856 descriptionDone = "Packages built",
857 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
862 factory.addStep(ShellCommand(
864 description = "Installing packages",
865 descriptionDone = "Packages installed",
866 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/install", "V=s"],
871 factory.addStep(ShellCommand(
873 description = "Indexing packages",
874 descriptionDone = "Packages indexed",
875 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES="],
880 factory.addStep(ShellCommand(
882 description = "Building and installing images",
883 descriptionDone = "Images built and installed",
884 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/install", "V=s"],
889 factory.addStep(ShellCommand(
891 description = "Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
892 command = "make -j1 buildinfo V=s || true",
897 factory.addStep(ShellCommand(
898 name = "json_overview_image_info",
899 description = "Generating profiles.json in target folder",
900 command = "make -j1 json_overview_image_info V=s || true",
905 factory.addStep(ShellCommand(
907 description = "Calculating checksums",
908 descriptionDone = "Checksums calculated",
909 command=["make", "-j1", "checksum", "V=s"],
914 factory.addStep(ShellCommand(
916 descriptionDone = "Kmod directory created",
917 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])],
918 haltOnFailure = True,
919 doStepIf = IsKmodArchiveEnabled,
922 factory.addStep(ShellCommand(
923 name = "kmodprepare",
924 description = "Preparing kmod archive",
925 descriptionDone = "Kmod archive prepared",
926 command=["rsync", "--include=/kmod-*.ipk", "--exclude=*", "-va",
927 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/", target=ts[0], subtarget=ts[1]),
928 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
929 haltOnFailure = True,
930 doStepIf = IsKmodArchiveEnabled,
933 factory.addStep(ShellCommand(
935 description = "Indexing kmod archive",
936 descriptionDone = "Kmod archive indexed",
937 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES=",
938 Interpolate("PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
940 haltOnFailure = True,
941 doStepIf = IsKmodArchiveEnabled,
945 factory.addStep(MasterShellCommand(
946 name = "signprepare",
947 descriptionDone = "Temporary signing directory prepared",
948 command = ["mkdir", "-p", "%s/signing" %(work_dir)],
949 haltOnFailure = True,
950 doStepIf = IsSignEnabled,
954 factory.addStep(ShellCommand(
956 description = "Packing files to sign",
957 descriptionDone = "Files to sign packed",
958 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]),
959 haltOnFailure = True,
960 doStepIf = IsSignEnabled,
963 factory.addStep(FileUpload(
964 workersrc = "sign.tar.gz",
965 masterdest = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
966 haltOnFailure = True,
967 doStepIf = IsSignEnabled,
970 factory.addStep(MasterShellCommand(
972 description = "Signing files",
973 descriptionDone = "Files signed",
974 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]), Interpolate("%(prop:branch)s")],
975 env = { 'CONFIG_INI': os.getenv("BUILDMASTER_CONFIG", "./config.ini") },
976 haltOnFailure = True,
977 doStepIf = IsSignEnabled,
980 factory.addStep(FileDownload(
981 name = "dlsigntargz",
982 mastersrc = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
983 workerdest = "sign.tar.gz",
984 haltOnFailure = True,
985 doStepIf = IsSignEnabled,
988 factory.addStep(ShellCommand(
990 description = "Unpacking signed files",
991 descriptionDone = "Signed files unpacked",
992 command = ["tar", "-xzf", "sign.tar.gz"],
993 haltOnFailure = True,
994 doStepIf = IsSignEnabled,
998 factory.addStep(ShellCommand(
1000 descriptionDone = "Upload directory structure prepared",
1001 command = ["mkdir", "-p", Interpolate("tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s", target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
1002 haltOnFailure = True
1005 factory.addStep(ShellCommand(
1006 name = "linkprepare",
1007 descriptionDone = "Repository symlink prepared",
1008 command = ["ln", "-s", "-f", Interpolate("../packages-%(kw:basever)s", basever=util.Transform(GetBaseVersion, Property("branch"))), Interpolate("tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix)],
1009 doStepIf = IsNoMasterBuild,
1010 haltOnFailure = True
1013 factory.addStep(ShellCommand(
1014 name = "kmoddirprepare",
1015 descriptionDone = "Kmod archive upload directory prepared",
1016 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)],
1017 haltOnFailure = True,
1018 doStepIf = IsKmodArchiveEnabled,
1021 factory.addStep(ShellCommand(
1023 description = "Uploading directory structure",
1024 descriptionDone = "Directory structure uploaded",
1025 command = ["rsync", "-az"] + rsync_defopts + ["tmp/upload/", Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url"))],
1026 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1027 haltOnFailure = True,
1032 # download remote sha256sums to 'target-sha256sums'
1033 factory.addStep(ShellCommand(
1034 name = "target-sha256sums",
1035 description = "Fetching remote sha256sums for target",
1036 descriptionDone = "Remote sha256sums for target fetched",
1037 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"],
1038 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1040 haltOnFailure = False,
1041 flunkOnFailure = False,
1042 warnOnFailure = False,
1045 # build list of files to upload
1046 factory.addStep(FileDownload(
1047 name = "dlsha2rsyncpl",
1048 mastersrc = scripts_dir + '/sha2rsync.pl',
1049 workerdest = "../sha2rsync.pl",
1053 factory.addStep(ShellCommand(
1055 description = "Building list of files to upload",
1056 descriptionDone = "List of files to upload built",
1057 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"],
1058 haltOnFailure = True,
1061 factory.addStep(FileDownload(
1062 name = "dlrsync.sh",
1063 mastersrc = scripts_dir + '/rsync.sh',
1064 workerdest = "../rsync.sh",
1068 # upload new files and update existing ones
1069 factory.addStep(ShellCommand(
1070 name = "targetupload",
1071 description = "Uploading target files",
1072 descriptionDone = "Target files uploaded",
1073 command=["../rsync.sh", "--exclude=/kmods/", "--files-from=rsynclist", "--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/", target=ts[0], subtarget=ts[1]),
1075 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)],
1076 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1077 haltOnFailure = True,
1081 # delete files which don't exist locally
1082 factory.addStep(ShellCommand(
1083 name = "targetprune",
1084 description = "Pruning target files",
1085 descriptionDone = "Target files pruned",
1086 command=["../rsync.sh", "--exclude=/kmods/", "--delete", "--existing", "--ignore-existing", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1087 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1088 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)],
1089 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1090 haltOnFailure = True,
1095 factory.addStep(ShellCommand(
1096 name = "kmodupload",
1097 description = "Uploading kmod archive",
1098 descriptionDone = "Kmod archive uploaded",
1099 command=["../rsync.sh", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1100 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1]),
1101 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)],
1102 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1103 haltOnFailure = True,
1106 doStepIf = IsKmodArchiveEnabled,
1109 factory.addStep(ShellCommand(
1110 name = "sourcelist",
1111 description = "Finding source archives to upload",
1112 descriptionDone = "Source archives to upload found",
1113 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1114 haltOnFailure = True
1117 factory.addStep(ShellCommand(
1118 name = "sourceupload",
1119 description = "Uploading source archives",
1120 descriptionDone = "Source archives uploaded",
1121 command=["../rsync.sh", "--files-from=sourcelist", "--size-only", "--delay-updates"] + rsync_defopts +
1122 [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"))],
1123 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")) },
1124 haltOnFailure = True,
1129 factory.addStep(ShellCommand(
1131 description = "Reporting disk usage",
1132 command=["df", "-h", "."],
1133 env={'LC_ALL': 'C'},
1134 haltOnFailure = False,
1135 flunkOnFailure = False,
1136 warnOnFailure = False,
1140 factory.addStep(ShellCommand(
1142 description = "Reporting estimated file space usage",
1143 command=["du", "-sh", "."],
1144 env={'LC_ALL': 'C'},
1145 haltOnFailure = False,
1146 flunkOnFailure = False,
1147 warnOnFailure = False,
1151 factory.addStep(ShellCommand(
1152 name = "ccachestat",
1153 description = "Reporting ccache stats",
1154 command=["ccache", "-s"],
1155 env = MakeEnv(overrides={ 'PATH': ["${PATH}", "./staging_dir/host/bin"] }),
1156 want_stderr = False,
1157 haltOnFailure = False,
1158 flunkOnFailure = False,
1159 warnOnFailure = False,
1160 hideStepIf = lambda r, s: r==results.FAILURE,
1163 c['builders'].append(BuilderConfig(name=target, workernames=workerNames, factory=factory, nextBuild=GetNextBuild))
1165 c['schedulers'].append(schedulers.Triggerable(name="trigger_%s" % target, builderNames=[ target ]))
1166 force_factory.addStep(steps.Trigger(
1167 name = "trigger_%s" % target,
1168 description = "Triggering %s build" % target,
1169 schedulerNames = [ "trigger_%s" % target ],
1170 set_properties = { "reason": Property("reason"), "tag": TagPropertyValue },
1171 doStepIf = IsTargetSelected(target)
1175 ####### STATUS TARGETS
1177 # 'status' is a list of Status Targets. The results of each build will be
1178 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1179 # including web pages, email senders, and IRC bots.
1181 if "status_bind" in inip1:
1183 'port': inip1.get("status_bind"),
1185 'waterfall_view': True,
1186 'console_view': True,
1191 if "status_user" in inip1 and "status_password" in inip1:
1192 c['www']['auth'] = util.UserPasswordAuth([
1193 (inip1.get("status_user"), inip1.get("status_password"))
1195 c['www']['authz'] = util.Authz(
1196 allowRules=[ util.AnyControlEndpointMatcher(role="admins") ],
1197 roleMatchers=[ util.RolesFromUsername(roles=["admins"], usernames=[inip1.get("status_user")]) ]
1201 if ini.has_section("irc"):
1203 irc_host = iniirc.get("host", None)
1204 irc_port = iniirc.getint("port", 6667)
1205 irc_chan = iniirc.get("channel", None)
1206 irc_nick = iniirc.get("nickname", None)
1207 irc_pass = iniirc.get("password", None)
1209 if irc_host and irc_nick and irc_chan:
1210 irc = reporters.IRC(irc_host, irc_nick,
1212 password = irc_pass,
1213 channels = [ irc_chan ],
1214 notify_events = [ 'exception', 'problem', 'recovery' ]
1217 c['services'].append(irc)
1219 c['revlink'] = util.RevlinkMatch([
1220 r'https://git.openwrt.org/openwrt/(.*).git'
1222 r'https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s')
1227 # This specifies what database buildbot uses to store its state. You can leave
1228 # this at its default for all but the largest installations.
1229 'db_url' : "sqlite:///state.sqlite",
1232 c['buildbotNetUsageData'] = None