phase1: FlattenList doesn't work on renderables
[buildbot.git] / phase1 / master.cfg
1 # -*- python -*-
2 # ex: set syntax=python:
3
4 import os
5 import re
6 import base64
7 import subprocess
8 import configparser
9
10 from dateutil.tz import tzutc
11 from datetime import datetime, timedelta
12
13 from twisted.internet import defer
14 from twisted.python import log
15
16 from buildbot import locks
17 from buildbot.data import resultspec
18 from buildbot.changes.gitpoller import GitPoller
19 from buildbot.config import BuilderConfig
20 from buildbot.plugins import reporters
21 from buildbot.plugins import schedulers
22 from buildbot.plugins import steps
23 from buildbot.plugins import util
24 from buildbot.process import properties
25 from buildbot.process import results
26 from buildbot.process.factory import BuildFactory
27 from buildbot.process.properties import Interpolate
28 from buildbot.process.properties import Property
29 from buildbot.schedulers.basic import AnyBranchScheduler
30 from buildbot.schedulers.forcesched import BaseParameter
31 from buildbot.schedulers.forcesched import ForceScheduler
32 from buildbot.schedulers.forcesched import ValidationError
33 from buildbot.steps.master import MasterShellCommand
34 from buildbot.steps.shell import SetPropertyFromCommand
35 from buildbot.steps.shell import ShellCommand
36 from buildbot.steps.source.git import Git
37 from buildbot.steps.transfer import FileDownload
38 from buildbot.steps.transfer import FileUpload
39 from buildbot.steps.transfer import StringDownload
40 from buildbot.worker import Worker
41
42
43 if not os.path.exists("twistd.pid"):
44 with open("twistd.pid", "w") as pidfile:
45 pidfile.write("{}".format(os.getpid()))
46
47 # This is a sample buildmaster config file. It must be installed as
48 # 'master.cfg' in your buildmaster's base directory.
49
50 ini = configparser.ConfigParser()
51 ini.read(os.getenv("BUILDMASTER_CONFIG", "./config.ini"))
52
53 if "general" not in ini or "phase1" not in ini:
54 raise ValueError("Fix your configuration")
55
56 inip1 = ini['phase1']
57
58 # Globals
59 work_dir = os.path.abspath(ini['general'].get("workdir", "."))
60 scripts_dir = os.path.abspath("../scripts")
61
62 repo_url = ini['repo'].get("url")
63
64 rsync_defopts = ["-v", "-4", "--timeout=120"]
65
66 #if rsync_bin_url.find("::") > 0 or rsync_bin_url.find("rsync://") == 0:
67 # rsync_bin_defopts += ["--contimeout=20"]
68
69 branches = {}
70
71 def ini_parse_branch(section):
72 b = {}
73 name = section.get("name")
74
75 if not name:
76 raise ValueError("missing 'name' in " + repr(section))
77 if name in branches:
78 raise ValueError("duplicate branch name in " + repr(section))
79
80 b["name"] = name
81 b["bin_url"] = section.get("binary_url")
82 b["bin_key"] = section.get("binary_password")
83
84 b["src_url"] = section.get("source_url")
85 b["src_key"] = section.get("source_password")
86
87 b["gpg_key"] = section.get("gpg_key")
88
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)
92
93 b["config_seed"] = section.get("config_seed")
94
95 b["kmod_archive"] = section.getboolean("kmod_archive", False)
96
97 branches[name] = b
98 log.msg("Configured branch: {}".format(name))
99
100 # PB port can be either a numeric port or a connection string
101 pb_port = inip1.get("port") or 9989
102
103 # This is the dictionary that the buildmaster pays attention to. We also use
104 # a shorter alias to save typing.
105 c = BuildmasterConfig = {}
106
107 ####### PROJECT IDENTITY
108
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.
112
113 c['title'] = ini['general'].get("title")
114 c['titleURL'] = ini['general'].get("title_url")
115
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
120 # without some help.
121
122 c['buildbotURL'] = inip1.get("buildbot_url")
123
124 ####### BUILDWORKERS
125
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.
129
130 c['workers'] = []
131 NetLocks = dict()
132
133 for section in ini.sections():
134 if section.startswith("branch "):
135 ini_parse_branch(ini[section])
136
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))
154
155 c['protocols'] = {'pb': {'port': pb_port}}
156
157 # coalesce builds
158 c['collapseRequests'] = True
159
160 # Reduce amount of backlog data
161 c['configurators'] = [util.JanitorConfigurator(
162 logHorizon=timedelta(days=3),
163 hour=6,
164 )]
165
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.
173
174 @returns: datetime instance or None, via Deferred
175 """
176
177 bldrid = yield bldr.getBuilderId()
178 completed = yield bldr.master.data.get(
179 ('builders', bldrid, 'buildrequests'),
180 [
181 resultspec.Filter('complete', 'eq', [True]),
182 resultspec.Filter('results', 'ne', [results.SKIPPED]),
183 ],
184 order=['-complete_at'], limit=1)
185 if not completed:
186 return
187
188 complete_at = completed[0]['complete_at']
189
190 last_build = yield bldr.master.data.get(
191 ('builds', ),
192 [
193 resultspec.Filter('builderid', 'eq', [bldrid]),
194 ],
195 order=['-started_at'], limit=1)
196
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
201
202 return complete_at
203
204 @defer.inlineCallbacks
205 def prioritizeBuilders(master, builders):
206 """Returns sorted list of builders by their last timestamp of completed and
207 not skipped build.
208
209 @returns: list of sorted builders
210 """
211
212 def is_building(bldr):
213 return bool(bldr.building) or bool(bldr.old_building)
214
215 def bldr_info(bldr):
216 d = defer.maybeDeferred(getNewestCompleteTime, bldr)
217 d.addCallback(lambda complete_at: (complete_at, bldr))
218 return d
219
220 def bldr_sort(item):
221 (complete_at, bldr) = item
222
223 if not complete_at:
224 date = datetime.min
225 complete_at = date.replace(tzinfo=tzutc())
226
227 if is_building(bldr):
228 date = datetime.max
229 complete_at = date.replace(tzinfo=tzutc())
230
231 return (complete_at, bldr.name)
232
233 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
234 results.sort(key=bldr_sort)
235
236 for r in results:
237 log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
238
239 return [r[1] for r in results]
240
241 c['prioritizeBuilders'] = prioritizeBuilders
242
243 ####### CHANGESOURCES
244
245 branchNames = [branches[b]["name"] for b in branches]
246
247 # find targets
248 targets = set()
249
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])
256
257 subprocess.call(["git", "clone", "-q", "--depth=1", "--branch="+branch, repo_url, sourcegit])
258
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)
262
263 while True:
264 line = findtargets.stdout.readline()
265 if not line:
266 break
267 ta = line.decode().strip().split(' ')
268 targets.add(ta[0])
269
270 subprocess.call(["rm", "-rf", sourcegit])
271
272 populateTargets()
273
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.
276
277 c['change_source'] = []
278 c['change_source'].append(GitPoller(
279 repo_url,
280 workdir=work_dir+'/work.git', branches=branchNames,
281 pollAtLaunch=True, pollinterval=300))
282
283 ####### SCHEDULERS
284
285 # Configure the Schedulers, which decide how to react to incoming changes. In this
286 # case, just kick off a 'basebuild' build
287
288 class TagChoiceParameter(BaseParameter):
289 spec_attributes = ["strict", "choices"]
290 type = "list"
291 strict = True
292
293 def __init__(self, name, label=None, **kw):
294 super().__init__(name, label, **kw)
295 self._choice_list = []
296
297 @property
298 def choices(self):
299 taglist = []
300 basever = re.search(r'-([0-9]+\.[0-9]+)$', "master") # XXX FIXME
301
302 if basever:
303 findtags = subprocess.Popen(
304 ['git', 'ls-remote', '--tags', repo_url],
305 stdout = subprocess.PIPE)
306
307 while True:
308 line = findtags.stdout.readline()
309
310 if not line:
311 break
312
313 tagver = re.search(r'\brefs/tags/v([0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$', line.decode().strip())
314
315 if tagver and tagver[1].find(basever[1]) == 0:
316 taglist.append(tagver[1])
317
318 taglist.sort(reverse=True, key=lambda tag: tag if re.search(r'-rc[0-9]+$', tag) else tag + '-z')
319 taglist.insert(0, '')
320
321 self._choice_list = taglist
322
323 return self._choice_list
324
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))
328 return s
329
330 c['schedulers'] = []
331 c['schedulers'].append(AnyBranchScheduler(
332 name = "all",
333 change_filter = util.ChangeFilter(branch=branchNames),
334 treeStableTimer = 15*60,
335 builderNames = list(targets)))
336
337 c['schedulers'].append(ForceScheduler(
338 name = "force",
339 buttonName = "Force builds",
340 label = "Force build details",
341 builderNames = [ "00_force_build" ],
342
343 codebases = [
344 util.CodebaseParameter(
345 "",
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 = "")
351 )
352 ],
353
354 reason = util.StringParameter(
355 name = "reason",
356 label = "Reason",
357 default = "Trigger build",
358 required = True,
359 size = 80
360 ),
361
362 properties = [
363 util.NestedParameter(
364 name="options",
365 label="Build Options",
366 layout="vertical",
367 fields=[
368 util.ChoiceStringParameter(
369 name = "target",
370 label = "Build target",
371 default = "all",
372 choices = set( "all" ) | targets
373 ),
374 TagChoiceParameter(
375 name = "tag",
376 label = "Build tag",
377 default = ""
378 )
379 ]
380 )
381 ]
382 ))
383
384 ####### BUILDERS
385
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.
389
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)
393
394 def IsNoMasterBuild(step):
395 return step.getProperty("branch") != "master"
396
397 def IsUsignEnabled(step):
398 branch = step.getProperty("branch")
399 return branch and branches[branch].get("usign_key")
400
401 def IsSignEnabled(step):
402 branch = step.getProperty("branch")
403 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
404
405 def IsKmodArchiveEnabled(step):
406 branch = step.getProperty("branch")
407 return branch and branches[branch].get("kmod_archive")
408
409 def GetBaseVersion(branch):
410 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
411 return branch.split('-')[1]
412 else:
413 return "master"
414
415 @properties.renderer
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
423 else:
424 return ""
425
426 @util.renderer
427 def GetConfigSeed(props):
428 branch = props.getProperty("branch")
429 return branch and branches[branch].get("config_seed") or ""
430
431 @util.renderer
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)
437
438 @util.renderer
439 def GetUsignKey(props):
440 branch = props.getProperty("branch")
441 return branch and branches[branch].get("usign_key")
442
443 def GetNextBuild(builder, requests):
444 for r in requests:
445 if r.properties:
446 # order tagged build first
447 if r.properties.hasProperty("tag"):
448 return r
449 # then order by branch order
450 pbranch = r.properties.getProperty("branch")
451 for name in branchNames:
452 if pbranch == name:
453 return r
454
455 r = requests[0]
456 log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
457 return r
458
459 def MakeEnv(overrides=None, tryccache=False):
460 env = {
461 'CCC': Interpolate("%(prop:cc_command:-gcc)s"),
462 'CCXX': Interpolate("%(prop:cxx_command:-g++)s"),
463 }
464 if tryccache:
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")
468 else:
469 env['CC'] = env['CCC']
470 env['CXX'] = env['CCXX']
471 env['CCACHE'] = ''
472 if overrides is not None:
473 env.update(overrides)
474 return env
475
476 @properties.renderer
477 def NetLockDl(props, extralock=None):
478 lock = None
479 locks = []
480 if props.hasProperty("dl_lock"):
481 lock = NetLocks[props["dl_lock"]]
482 if lock is not None:
483 locks.append(lock.access('exclusive'))
484 if extralock is not None:
485 locks.append(extralock)
486 return locks
487
488 @properties.renderer
489 def NetLockUl(props):
490 lock = None
491 if props.hasProperty("ul_lock"):
492 lock = NetLocks[props["ul_lock"]]
493 if lock is not None:
494 return [lock.access('exclusive')]
495 else:
496 return []
497
498 @util.renderer
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")
504 return None
505
506 def IsTargetSelected(target):
507 def CheckTargetProperty(step):
508 try:
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:
513 return False
514 except KeyError:
515 pass
516
517 return True
518
519 return CheckTargetProperty
520
521 @util.renderer
522 def UsignSec2Pub(props):
523 branch = props.getProperty("branch")
524 try:
525 comment = branches[branch].get("usign_comment") or "untrusted comment: secret key"
526 seckey = branches[branch].get("usign_key")
527 seckey = base64.b64decode(seckey)
528 except:
529 return None
530
531 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
532 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
533
534
535 c['builders'] = []
536
537 dlLock = locks.WorkerLock("worker_dl")
538
539 workerNames = [ ]
540
541 for worker in c['workers']:
542 workerNames.append(worker.workername)
543
544 force_factory = BuildFactory()
545
546 c['builders'].append(BuilderConfig(
547 name = "00_force_build",
548 workernames = workerNames,
549 factory = force_factory))
550
551 for target in targets:
552 ts = target.split('/')
553
554 factory = BuildFactory()
555
556 # setup shared work directory if required
557 factory.addStep(ShellCommand(
558 name = "sharedwd",
559 description = "Setting up shared work directory",
560 command = 'test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
561 workdir = ".",
562 haltOnFailure = True))
563
564 # find number of cores
565 factory.addStep(SetPropertyFromCommand(
566 name = "nproc",
567 property = "nproc",
568 description = "Finding number of CPUs",
569 command = ["nproc"]))
570
571 # find gcc and g++ compilers
572 factory.addStep(FileDownload(
573 name = "dlfindbinpl",
574 mastersrc = scripts_dir + '/findbin.pl',
575 workerdest = "../findbin.pl",
576 mode = 0o755))
577
578 factory.addStep(SetPropertyFromCommand(
579 name = "gcc",
580 property = "cc_command",
581 description = "Finding gcc command",
582 command = [
583 "../findbin.pl", "gcc", "", "",
584 ],
585 haltOnFailure = True))
586
587 factory.addStep(SetPropertyFromCommand(
588 name = "g++",
589 property = "cxx_command",
590 description = "Finding g++ command",
591 command = [
592 "../findbin.pl", "g++", "", "",
593 ],
594 haltOnFailure = True))
595
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,
604 ))
605
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))
613
614 # check out the source
615 # Git() runs:
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'
619 factory.addStep(Git(
620 name = "git",
621 repourl = repo_url,
622 mode = 'full',
623 method = 'fresh',
624 locks = NetLockDl,
625 haltOnFailure = True,
626 ))
627
628 # update remote refs
629 factory.addStep(ShellCommand(
630 name = "fetchrefs",
631 description = "Fetching Git remote refs",
632 command = ["git", "fetch", "origin", Interpolate("+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s")],
633 haltOnFailure = True
634 ))
635
636 # switch to tag
637 factory.addStep(ShellCommand(
638 name = "switchtag",
639 description = "Checking out Git tag",
640 command = ["git", "checkout", Interpolate("tags/v%(prop:tag:-)s")],
641 haltOnFailure = True,
642 doStepIf = IsTaggingRequested
643 ))
644
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(
648 name = "gitverify",
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))
652
653 factory.addStep(ShellCommand(
654 name = "rmtmp",
655 description = "Remove tmp folder",
656 command=["rm", "-rf", "tmp/"]))
657
658 # feed
659 factory.addStep(ShellCommand(
660 name = "rmfeedlinks",
661 description = "Remove feed symlinks",
662 command=["rm", "-rf", "package/feeds/"]))
663
664 factory.addStep(StringDownload(
665 name = "ccachecc",
666 s = '#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
667 workerdest = "../ccache_cc.sh",
668 mode = 0o755,
669 ))
670
671 factory.addStep(StringDownload(
672 name = "ccachecxx",
673 s = '#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
674 workerdest = "../ccache_cxx.sh",
675 mode = 0o755,
676 ))
677
678 # feed
679 factory.addStep(ShellCommand(
680 name = "updatefeeds",
681 description = "Updating feeds",
682 command=["./scripts/feeds", "update"],
683 env = MakeEnv(tryccache=True),
684 haltOnFailure = True,
685 locks = NetLockDl,
686 ))
687
688 # feed
689 factory.addStep(ShellCommand(
690 name = "installfeeds",
691 description = "Installing feeds",
692 command=["./scripts/feeds", "install", "-a"],
693 env = MakeEnv(tryccache=True),
694 haltOnFailure = True
695 ))
696
697 # seed config
698 factory.addStep(StringDownload(
699 name = "dlconfigseed",
700 s = Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
701 workerdest = ".config",
702 mode = 0o644
703 ))
704
705 # configure
706 factory.addStep(ShellCommand(
707 name = "newconfig",
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)
710 ))
711
712 factory.addStep(ShellCommand(
713 name = "delbin",
714 description = "Removing output directory",
715 command = ["rm", "-rf", "bin/"]
716 ))
717
718 factory.addStep(ShellCommand(
719 name = "defconfig",
720 description = "Populating .config",
721 command = ["make", "defconfig"],
722 env = MakeEnv()
723 ))
724
725 # check arch - exit early if does not exist - NB: some targets do not define CONFIG_TARGET_target_subtarget
726 factory.addStep(ShellCommand(
727 name = "checkarch",
728 description = "Checking architecture",
729 command = 'grep -sq CONFIG_TARGET_%s=y .config && grep -sq CONFIG_TARGET_SUBTARGET=\\"%s\\" .config' %(ts[0], ts[1]),
730 logEnviron = False,
731 want_stdout = False,
732 want_stderr = False,
733 haltOnFailure = True,
734 flunkOnFailure = False, # this is not a build FAILURE
735 ))
736
737 # find libc suffix
738 factory.addStep(SetPropertyFromCommand(
739 name = "libc",
740 property = "libc",
741 description = "Finding libc suffix",
742 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
743
744 # install build key
745 factory.addStep(StringDownload(
746 name = "dlkeybuildpub",
747 s = Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
748 workerdest = "key-build.pub",
749 mode = 0o600,
750 doStepIf = IsUsignEnabled,
751 ))
752
753 factory.addStep(StringDownload(
754 name = "dlkeybuild",
755 s = "# fake private key",
756 workerdest = "key-build",
757 mode = 0o600,
758 doStepIf = IsUsignEnabled,
759 ))
760
761 factory.addStep(StringDownload(
762 name = "dlkeybuilducert",
763 s = "# fake certificate",
764 workerdest = "key-build.ucert",
765 mode = 0o600,
766 doStepIf = IsUsignEnabled,
767 ))
768
769 # prepare dl
770 factory.addStep(ShellCommand(
771 name = "dldir",
772 description = "Preparing dl/",
773 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
774 logEnviron = False,
775 want_stdout = False
776 ))
777
778 # prepare tar
779 factory.addStep(ShellCommand(
780 name = "dltar",
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),
784 haltOnFailure = True
785 ))
786
787 # populate dl
788 factory.addStep(ShellCommand(
789 name = "dlrun",
790 description = "Populating dl/",
791 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
792 env = MakeEnv(),
793 logEnviron = False,
794 locks = NetLockDl.withArgs(dlLock.access('exclusive')),
795 ))
796
797 factory.addStep(ShellCommand(
798 name = "cleanbase",
799 description = "Cleaning base-files",
800 command=["make", "package/base-files/clean", "V=s"]
801 ))
802
803 # build
804 factory.addStep(ShellCommand(
805 name = "tools",
806 description = "Building and installing tools",
807 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/install", "V=s"],
808 env = MakeEnv(tryccache=True),
809 haltOnFailure = True
810 ))
811
812 factory.addStep(ShellCommand(
813 name = "toolchain",
814 description = "Building and installing toolchain",
815 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "toolchain/install", "V=s"],
816 env = MakeEnv(),
817 haltOnFailure = True
818 ))
819
820 factory.addStep(ShellCommand(
821 name = "kmods",
822 description = "Building kmods",
823 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
824 env = MakeEnv(),
825 haltOnFailure = True
826 ))
827
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") }
835 ))
836
837 factory.addStep(ShellCommand(
838 name = "pkgclean",
839 description = "Cleaning up package build",
840 command=["make", "package/cleanup", "V=s"]
841 ))
842
843 factory.addStep(ShellCommand(
844 name = "pkgbuild",
845 description = "Building packages",
846 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
847 env = MakeEnv(),
848 haltOnFailure = True
849 ))
850
851 factory.addStep(ShellCommand(
852 name = "pkginstall",
853 description = "Installing packages",
854 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/install", "V=s"],
855 env = MakeEnv(),
856 haltOnFailure = True
857 ))
858
859 factory.addStep(ShellCommand(
860 name = "pkgindex",
861 description = "Indexing packages",
862 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES="],
863 env = MakeEnv(),
864 haltOnFailure = True
865 ))
866
867 factory.addStep(ShellCommand(
868 name = "images",
869 description = "Building and installing images",
870 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/install", "V=s"],
871 env = MakeEnv(),
872 haltOnFailure = True
873 ))
874
875 factory.addStep(ShellCommand(
876 name = "buildinfo",
877 description = "Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
878 command = "make -j1 buildinfo V=s || true",
879 env = MakeEnv(),
880 haltOnFailure = True
881 ))
882
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",
887 env = MakeEnv(),
888 haltOnFailure = True
889 ))
890
891 factory.addStep(ShellCommand(
892 name = "checksums",
893 description = "Calculating checksums",
894 command=["make", "-j1", "checksum", "V=s"],
895 env = MakeEnv(),
896 haltOnFailure = True
897 ))
898
899 factory.addStep(ShellCommand(
900 name = "kmoddir",
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,
905 ))
906
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,
915 ))
916
917 factory.addStep(ShellCommand(
918 name = "kmodindex",
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])],
922 env = MakeEnv(),
923 haltOnFailure = True,
924 doStepIf = IsKmodArchiveEnabled,
925 ))
926
927 # sign
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,
934
935 ))
936
937 factory.addStep(ShellCommand(
938 name = "signpack",
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,
943 ))
944
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,
950 ))
951
952 factory.addStep(MasterShellCommand(
953 name = "signfiles",
954 description = "Signing files",
955 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]), Interpolate("%(prop:branch)s")],
956 env = { 'CONFIG_INI': os.getenv("BUILDMASTER_CONFIG", "./config.ini") },
957 haltOnFailure = True,
958 doStepIf = IsSignEnabled,
959 ))
960
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,
967 ))
968
969 factory.addStep(ShellCommand(
970 name = "signunpack",
971 description = "Unpacking signed files",
972 command = ["tar", "-xzf", "sign.tar.gz"],
973 haltOnFailure = True,
974 doStepIf = IsSignEnabled,
975 ))
976
977 # upload
978 factory.addStep(ShellCommand(
979 name = "dirprepare",
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)],
982 haltOnFailure = True
983 ))
984
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,
990 haltOnFailure = True
991 ))
992
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,
999 ))
1000
1001 factory.addStep(ShellCommand(
1002 name = "dirupload",
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,
1007 logEnviron = False,
1008 locks = NetLockUl,
1009 ))
1010
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")) },
1017 logEnviron = False,
1018 haltOnFailure = False,
1019 flunkOnFailure = False,
1020 warnOnFailure = False,
1021 ))
1022
1023 # build list of files to upload
1024 factory.addStep(FileDownload(
1025 name = "dlsha2rsyncpl",
1026 mastersrc = scripts_dir + '/sha2rsync.pl',
1027 workerdest = "../sha2rsync.pl",
1028 mode = 0o755,
1029 ))
1030
1031 factory.addStep(ShellCommand(
1032 name = "buildlist",
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,
1036 ))
1037
1038 factory.addStep(FileDownload(
1039 name = "dlrsync.sh",
1040 mastersrc = scripts_dir + '/rsync.sh',
1041 workerdest = "../rsync.sh",
1042 mode = 0o755
1043 ))
1044
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,
1054 logEnviron = False,
1055 ))
1056
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,
1066 logEnviron = False,
1067 locks = NetLockUl,
1068 ))
1069
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,
1078 logEnviron = False,
1079 locks = NetLockUl,
1080 doStepIf = IsKmodArchiveEnabled,
1081 ))
1082
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
1088 ))
1089
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,
1097 logEnviron = False,
1098 locks = NetLockUl,
1099 ))
1100
1101 factory.addStep(ShellCommand(
1102 name = "df",
1103 description = "Reporting disk usage",
1104 command=["df", "-h", "."],
1105 env={'LC_ALL': 'C'},
1106 haltOnFailure = False,
1107 flunkOnFailure = False,
1108 warnOnFailure = False,
1109 alwaysRun = True
1110 ))
1111
1112 factory.addStep(ShellCommand(
1113 name = "du",
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,
1120 alwaysRun = True
1121 ))
1122
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,
1132 alwaysRun = True,
1133 ))
1134
1135 c['builders'].append(BuilderConfig(name=target, workernames=workerNames, factory=factory, nextBuild=GetNextBuild))
1136
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)
1144 ))
1145
1146
1147 ####### STATUS TARGETS
1148
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.
1152
1153 if "status_bind" in inip1:
1154 c['www'] = {
1155 'port': inip1.get("status_bind"),
1156 'plugins': {
1157 'waterfall_view': True,
1158 'console_view': True,
1159 'grid_view': True
1160 }
1161 }
1162
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"))
1166 ])
1167 c['www']['authz'] = util.Authz(
1168 allowRules=[ util.AnyControlEndpointMatcher(role="admins") ],
1169 roleMatchers=[ util.RolesFromUsername(roles=["admins"], usernames=[inip1.get("status_user")]) ]
1170 )
1171
1172 c['services'] = []
1173 if ini.has_section("irc"):
1174 iniirc = ini['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)
1180
1181 if irc_host and irc_nick and irc_chan:
1182 irc = reporters.IRC(irc_host, irc_nick,
1183 port = irc_port,
1184 password = irc_pass,
1185 channels = [ irc_chan ],
1186 notify_events = [ 'exception', 'problem', 'recovery' ]
1187 )
1188
1189 c['services'].append(irc)
1190
1191 c['revlink'] = util.RevlinkMatch([
1192 r'https://git.openwrt.org/openwrt/(.*).git'
1193 ],
1194 r'https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s')
1195
1196 ####### DB URL
1197
1198 c['db'] = {
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",
1202 }
1203
1204 c['buildbotNetUsageData'] = None