9f4a343df6dd9c03f5b01a79e8eddcafee9cdf6e
[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 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
42
43
44 if not os.path.exists("twistd.pid"):
45 with open("twistd.pid", "w") as pidfile:
46 pidfile.write("{}".format(os.getpid()))
47
48 # This is a sample buildmaster config file. It must be installed as
49 # 'master.cfg' in your buildmaster's base directory.
50
51 ini = configparser.ConfigParser()
52 ini.read(os.getenv("BUILDMASTER_CONFIG", "./config.ini"))
53
54 if "general" not in ini or "phase1" not in ini:
55 raise ValueError("Fix your configuration")
56
57 inip1 = ini['phase1']
58
59 # Globals
60 work_dir = os.path.abspath(ini['general'].get("workdir", "."))
61 scripts_dir = os.path.abspath("../scripts")
62
63 repo_url = ini['repo'].get("url")
64 repo_branch = ini['repo'].get("branch", "master")
65
66 rsync_defopts = ["-v", "-4", "--timeout=120"]
67
68 #if rsync_bin_url.find("::") > 0 or rsync_bin_url.find("rsync://") == 0:
69 # rsync_bin_defopts += ["--contimeout=20"]
70
71 branches = {}
72
73 def ini_parse_branch(section):
74 b = {}
75 name = section.get("name")
76
77 if not name:
78 raise ValueError("missing 'name' in " + repr(section))
79 if name in branches:
80 raise ValueError("duplicate branch name in " + repr(section))
81
82 b["name"] = name
83 b["bin_url"] = section.get("binary_url")
84 b["bin_key"] = section.get("binary_password")
85
86 b["src_url"] = section.get("source_url")
87 b["src_key"] = section.get("source_password")
88
89 b["gpg_key"] = section.get("gpg_key")
90
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)
94
95 b["config_seed"] = section.get("config_seed")
96
97 b["kmod_archive"] = section.getboolean("kmod_archive", False)
98
99 branches[name] = b
100 log.msg("Configured branch: {}".format(name))
101
102 # PB port can be either a numeric port or a connection string
103 pb_port = inip1.get("port") or 9989
104
105 # This is the dictionary that the buildmaster pays attention to. We also use
106 # a shorter alias to save typing.
107 c = BuildmasterConfig = {}
108
109 ####### PROJECT IDENTITY
110
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.
114
115 c['title'] = ini['general'].get("title")
116 c['titleURL'] = ini['general'].get("title_url")
117
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
122 # without some help.
123
124 c['buildbotURL'] = inip1.get("buildbot_url")
125
126 ####### BUILDWORKERS
127
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.
131
132 c['workers'] = []
133 NetLocks = dict()
134
135 for section in ini.sections():
136 if section.startswith("branch "):
137 ini_parse_branch(ini[section])
138
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))
156
157 c['protocols'] = {'pb': {'port': pb_port}}
158
159 # coalesce builds
160 c['collapseRequests'] = True
161
162 # Reduce amount of backlog data
163 c['configurators'] = [util.JanitorConfigurator(
164 logHorizon=timedelta(days=3),
165 hour=6,
166 )]
167
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.
175
176 @returns: datetime instance or None, via Deferred
177 """
178
179 bldrid = yield bldr.getBuilderId()
180 completed = yield bldr.master.data.get(
181 ('builders', bldrid, 'buildrequests'),
182 [
183 resultspec.Filter('complete', 'eq', [True]),
184 resultspec.Filter('results', 'ne', [results.SKIPPED]),
185 ],
186 order=['-complete_at'], limit=1)
187 if not completed:
188 return
189
190 complete_at = completed[0]['complete_at']
191
192 last_build = yield bldr.master.data.get(
193 ('builds', ),
194 [
195 resultspec.Filter('builderid', 'eq', [bldrid]),
196 ],
197 order=['-started_at'], limit=1)
198
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
203
204 return complete_at
205
206 @defer.inlineCallbacks
207 def prioritizeBuilders(master, builders):
208 """Returns sorted list of builders by their last timestamp of completed and
209 not skipped build.
210
211 @returns: list of sorted builders
212 """
213
214 def is_building(bldr):
215 return bool(bldr.building) or bool(bldr.old_building)
216
217 def bldr_info(bldr):
218 d = defer.maybeDeferred(getNewestCompleteTime, bldr)
219 d.addCallback(lambda complete_at: (complete_at, bldr))
220 return d
221
222 def bldr_sort(item):
223 (complete_at, bldr) = item
224
225 if not complete_at:
226 date = datetime.min
227 complete_at = date.replace(tzinfo=tzutc())
228
229 if is_building(bldr):
230 date = datetime.max
231 complete_at = date.replace(tzinfo=tzutc())
232
233 return (complete_at, bldr.name)
234
235 results = yield defer.gatherResults([bldr_info(bldr) for bldr in builders])
236 results.sort(key=bldr_sort)
237
238 for r in results:
239 log.msg("prioritizeBuilders: {:>20} complete_at: {}".format(r[1].name, r[0]))
240
241 return [r[1] for r in results]
242
243 c['prioritizeBuilders'] = prioritizeBuilders
244
245 ####### CHANGESOURCES
246
247
248 # find targets
249 targets = [ ]
250
251 def populateTargets():
252 sourcegit = work_dir + '/source.git'
253 if os.path.isdir(sourcegit):
254 subprocess.call(["rm", "-rf", sourcegit])
255
256 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, sourcegit])
257
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)
261
262 while True:
263 line = findtargets.stdout.readline()
264 if not line:
265 break
266 ta = line.decode().strip().split(' ')
267 targets.append(ta[0])
268
269 subprocess.call(["rm", "-rf", sourcegit])
270
271 populateTargets()
272
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.
275
276 c['change_source'] = []
277 c['change_source'].append(GitPoller(
278 repo_url,
279 workdir=work_dir+'/work.git', branch=repo_branch,
280 pollinterval=300))
281
282 ####### SCHEDULERS
283
284 # Configure the Schedulers, which decide how to react to incoming changes. In this
285 # case, just kick off a 'basebuild' build
286
287 class TagChoiceParameter(BaseParameter):
288 spec_attributes = ["strict", "choices"]
289 type = "list"
290 strict = True
291
292 def __init__(self, name, label=None, **kw):
293 super().__init__(name, label, **kw)
294 self._choice_list = []
295
296 @property
297 def choices(self):
298 taglist = []
299 basever = re.search(r'-([0-9]+\.[0-9]+)$', repo_branch)
300
301 if basever:
302 findtags = subprocess.Popen(
303 ['git', 'ls-remote', '--tags', repo_url],
304 stdout = subprocess.PIPE)
305
306 while True:
307 line = findtags.stdout.readline()
308
309 if not line:
310 break
311
312 tagver = re.search(r'\brefs/tags/v([0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$', line.decode().strip())
313
314 if tagver and tagver[1].find(basever[1]) == 0:
315 taglist.append(tagver[1])
316
317 taglist.sort(reverse=True, key=lambda tag: tag if re.search(r'-rc[0-9]+$', tag) else tag + '-z')
318 taglist.insert(0, '')
319
320 self._choice_list = taglist
321
322 return self._choice_list
323
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))
327 return s
328
329 c['schedulers'] = []
330 c['schedulers'].append(SingleBranchScheduler(
331 name = "all",
332 change_filter = filter.ChangeFilter(branch=repo_branch),
333 treeStableTimer = 60,
334 builderNames = targets))
335
336 c['schedulers'].append(ForceScheduler(
337 name = "force",
338 buttonName = "Force builds",
339 label = "Force build details",
340 builderNames = [ "00_force_build" ],
341
342 codebases = [
343 util.CodebaseParameter(
344 "",
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 = "")
350 )
351 ],
352
353 reason = util.StringParameter(
354 name = "reason",
355 label = "Reason",
356 default = "Trigger build",
357 required = True,
358 size = 80
359 ),
360
361 properties = [
362 util.NestedParameter(
363 name="options",
364 label="Build Options",
365 layout="vertical",
366 fields=[
367 util.ChoiceStringParameter(
368 name = "target",
369 label = "Build target",
370 default = "all",
371 choices = [ "all" ] + targets
372 ),
373 TagChoiceParameter(
374 name = "tag",
375 label = "Build tag",
376 default = ""
377 )
378 ]
379 )
380 ]
381 ))
382
383 ####### BUILDERS
384
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.
388
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)
392
393 def IsNoMasterBuild(step):
394 return step.getProperty("branch") != "master"
395
396 def IsUsignEnabled(step):
397 branch = step.getProperty("branch")
398 return branch and branches[branch].get("usign_key")
399
400 def IsSignEnabled(step):
401 branch = step.getProperty("branch")
402 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
403
404 def IsKmodArchiveEnabled(step):
405 branch = step.getProperty("branch")
406 return branch and branches[branch].get("kmod_archive")
407
408 def GetBaseVersion(branch):
409 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
410 return branch.split('-')[1]
411 else:
412 return "master"
413
414 @properties.renderer
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
422 else:
423 return ""
424
425 @util.renderer
426 def GetConfigSeed(props):
427 branch = props.getProperty("branch")
428 return branch and branches[branch].get("config_seed") or ""
429
430 @util.renderer
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)
436
437 @util.renderer
438 def GetUsignKey(props):
439 branch = props.getProperty("branch")
440 return branch and branches[branch].get("usign_key")
441
442 def GetNextBuild(builder, requests):
443 for r in requests:
444 if r.properties and r.properties.hasProperty("tag"):
445 return r
446
447 r = requests[0]
448 log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
449 return r
450
451 def MakeEnv(overrides=None, tryccache=False):
452 env = {
453 'CCC': Interpolate("%(prop:cc_command:-gcc)s"),
454 'CCXX': Interpolate("%(prop:cxx_command:-g++)s"),
455 }
456 if tryccache:
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")
460 else:
461 env['CC'] = env['CCC']
462 env['CXX'] = env['CCXX']
463 env['CCACHE'] = ''
464 if overrides is not None:
465 env.update(overrides)
466 return env
467
468 @properties.renderer
469 def NetLockDl(props):
470 lock = None
471 if props.hasProperty("dl_lock"):
472 lock = NetLocks[props["dl_lock"]]
473 if lock is not None:
474 return [lock.access('exclusive')]
475 else:
476 return []
477
478 @properties.renderer
479 def NetLockUl(props):
480 lock = None
481 if props.hasProperty("ul_lock"):
482 lock = NetLocks[props["ul_lock"]]
483 if lock is not None:
484 return [lock.access('exclusive')]
485 else:
486 return []
487
488 @util.renderer
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")
494 return None
495
496 def IsTargetSelected(target):
497 def CheckTargetProperty(step):
498 try:
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:
503 return False
504 except KeyError:
505 pass
506
507 return True
508
509 return CheckTargetProperty
510
511 @util.renderer
512 def UsignSec2Pub(props):
513 branch = props.getProperty("branch")
514 try:
515 comment = branches[branch].get("usign_comment") or "untrusted comment: secret key"
516 seckey = branches[branch].get("usign_key")
517 seckey = base64.b64decode(seckey)
518 except:
519 return None
520
521 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
522 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
523
524
525 c['builders'] = []
526
527 dlLock = locks.WorkerLock("worker_dl")
528
529 workerNames = [ ]
530
531 for worker in c['workers']:
532 workerNames.append(worker.workername)
533
534 force_factory = BuildFactory()
535
536 c['builders'].append(BuilderConfig(
537 name = "00_force_build",
538 workernames = workerNames,
539 factory = force_factory))
540
541 for target in targets:
542 ts = target.split('/')
543
544 factory = BuildFactory()
545
546 # setup shared work directory if required
547 factory.addStep(ShellCommand(
548 name = "sharedwd",
549 description = "Setting up shared work directory",
550 command = 'test -L "$PWD" || (mkdir -p ../shared-workdir && rm -rf "$PWD" && ln -s shared-workdir "$PWD")',
551 workdir = ".",
552 haltOnFailure = True))
553
554 # find number of cores
555 factory.addStep(SetPropertyFromCommand(
556 name = "nproc",
557 property = "nproc",
558 description = "Finding number of CPUs",
559 command = ["nproc"]))
560
561 # find gcc and g++ compilers
562 factory.addStep(FileDownload(
563 name = "dlfindbinpl",
564 mastersrc = scripts_dir + '/findbin.pl',
565 workerdest = "../findbin.pl",
566 mode = 0o755))
567
568 factory.addStep(SetPropertyFromCommand(
569 name = "gcc",
570 property = "cc_command",
571 description = "Finding gcc command",
572 command = [
573 "../findbin.pl", "gcc", "", "",
574 ],
575 haltOnFailure = True))
576
577 factory.addStep(SetPropertyFromCommand(
578 name = "g++",
579 property = "cxx_command",
580 description = "Finding g++ command",
581 command = [
582 "../findbin.pl", "g++", "", "",
583 ],
584 haltOnFailure = True))
585
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,
594 ))
595
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))
603
604 # check out the source
605 # Git() runs:
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'
609 factory.addStep(Git(
610 name = "git",
611 repourl = repo_url,
612 mode = 'full',
613 method = 'fresh',
614 locks = NetLockDl,
615 haltOnFailure = True,
616 ))
617
618 # update remote refs
619 factory.addStep(ShellCommand(
620 name = "fetchrefs",
621 description = "Fetching Git remote refs",
622 command = ["git", "fetch", "origin", Interpolate("+refs/heads/%(prop:branch)s:refs/remotes/origin/%(prop:branch)s")],
623 haltOnFailure = True
624 ))
625
626 # switch to tag
627 factory.addStep(ShellCommand(
628 name = "switchtag",
629 description = "Checking out Git tag",
630 command = ["git", "checkout", Interpolate("tags/v%(prop:tag:-)s")],
631 haltOnFailure = True,
632 doStepIf = IsTaggingRequested
633 ))
634
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(
638 name = "gitverify",
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))
642
643 factory.addStep(ShellCommand(
644 name = "rmtmp",
645 description = "Remove tmp folder",
646 command=["rm", "-rf", "tmp/"]))
647
648 # feed
649 factory.addStep(ShellCommand(
650 name = "rmfeedlinks",
651 description = "Remove feed symlinks",
652 command=["rm", "-rf", "package/feeds/"]))
653
654 factory.addStep(StringDownload(
655 name = "ccachecc",
656 s = '#!/bin/sh\nexec ${CCACHE} ${CCC} "$@"\n',
657 workerdest = "../ccache_cc.sh",
658 mode = 0o755,
659 ))
660
661 factory.addStep(StringDownload(
662 name = "ccachecxx",
663 s = '#!/bin/sh\nexec ${CCACHE} ${CCXX} "$@"\n',
664 workerdest = "../ccache_cxx.sh",
665 mode = 0o755,
666 ))
667
668 # feed
669 factory.addStep(ShellCommand(
670 name = "updatefeeds",
671 description = "Updating feeds",
672 command=["./scripts/feeds", "update"],
673 env = MakeEnv(tryccache=True),
674 haltOnFailure = True,
675 locks = NetLockDl,
676 ))
677
678 # feed
679 factory.addStep(ShellCommand(
680 name = "installfeeds",
681 description = "Installing feeds",
682 command=["./scripts/feeds", "install", "-a"],
683 env = MakeEnv(tryccache=True),
684 haltOnFailure = True
685 ))
686
687 # seed config
688 factory.addStep(StringDownload(
689 name = "dlconfigseed",
690 s = Interpolate("%(kw:seed)s\n", seed=GetConfigSeed),
691 workerdest = ".config",
692 mode = 0o644
693 ))
694
695 # configure
696 factory.addStep(ShellCommand(
697 name = "newconfig",
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)
700 ))
701
702 factory.addStep(ShellCommand(
703 name = "delbin",
704 description = "Removing output directory",
705 command = ["rm", "-rf", "bin/"]
706 ))
707
708 factory.addStep(ShellCommand(
709 name = "defconfig",
710 description = "Populating .config",
711 command = ["make", "defconfig"],
712 env = MakeEnv()
713 ))
714
715 # check arch
716 factory.addStep(ShellCommand(
717 name = "checkarch",
718 description = "Checking architecture",
719 command = ["grep", "-sq", "CONFIG_TARGET_%s=y" %(ts[0]), ".config"],
720 logEnviron = False,
721 want_stdout = False,
722 want_stderr = False,
723 haltOnFailure = True
724 ))
725
726 # find libc suffix
727 factory.addStep(SetPropertyFromCommand(
728 name = "libc",
729 property = "libc",
730 description = "Finding libc suffix",
731 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
732
733 # install build key
734 factory.addStep(StringDownload(
735 name = "dlkeybuildpub",
736 s = Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
737 workerdest = "key-build.pub",
738 mode = 0o600,
739 doStepIf = IsUsignEnabled,
740 ))
741
742 factory.addStep(StringDownload(
743 name = "dlkeybuild",
744 s = "# fake private key",
745 workerdest = "key-build",
746 mode = 0o600,
747 doStepIf = IsUsignEnabled,
748 ))
749
750 factory.addStep(StringDownload(
751 name = "dlkeybuilducert",
752 s = "# fake certificate",
753 workerdest = "key-build.ucert",
754 mode = 0o600,
755 doStepIf = IsUsignEnabled,
756 ))
757
758 # prepare dl
759 factory.addStep(ShellCommand(
760 name = "dldir",
761 description = "Preparing dl/",
762 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
763 logEnviron = False,
764 want_stdout = False
765 ))
766
767 # prepare tar
768 factory.addStep(ShellCommand(
769 name = "dltar",
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),
773 haltOnFailure = True
774 ))
775
776 # populate dl
777 factory.addStep(ShellCommand(
778 name = "dlrun",
779 description = "Populating dl/",
780 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
781 env = MakeEnv(),
782 logEnviron = False,
783 locks = properties.FlattenList(NetLockDl, [dlLock.access('exclusive')]),
784 ))
785
786 factory.addStep(ShellCommand(
787 name = "cleanbase",
788 description = "Cleaning base-files",
789 command=["make", "package/base-files/clean", "V=s"]
790 ))
791
792 # build
793 factory.addStep(ShellCommand(
794 name = "tools",
795 description = "Building and installing tools",
796 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/install", "V=s"],
797 env = MakeEnv(tryccache=True),
798 haltOnFailure = True
799 ))
800
801 factory.addStep(ShellCommand(
802 name = "toolchain",
803 description = "Building and installing toolchain",
804 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "toolchain/install", "V=s"],
805 env = MakeEnv(),
806 haltOnFailure = True
807 ))
808
809 factory.addStep(ShellCommand(
810 name = "kmods",
811 description = "Building kmods",
812 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
813 env = MakeEnv(),
814 haltOnFailure = True
815 ))
816
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") }
824 ))
825
826 factory.addStep(ShellCommand(
827 name = "pkgclean",
828 description = "Cleaning up package build",
829 command=["make", "package/cleanup", "V=s"]
830 ))
831
832 factory.addStep(ShellCommand(
833 name = "pkgbuild",
834 description = "Building packages",
835 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
836 env = MakeEnv(),
837 haltOnFailure = True
838 ))
839
840 factory.addStep(ShellCommand(
841 name = "pkginstall",
842 description = "Installing packages",
843 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/install", "V=s"],
844 env = MakeEnv(),
845 haltOnFailure = True
846 ))
847
848 factory.addStep(ShellCommand(
849 name = "pkgindex",
850 description = "Indexing packages",
851 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES="],
852 env = MakeEnv(),
853 haltOnFailure = True
854 ))
855
856 factory.addStep(ShellCommand(
857 name = "images",
858 description = "Building and installing images",
859 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/install", "V=s"],
860 env = MakeEnv(),
861 haltOnFailure = True
862 ))
863
864 factory.addStep(ShellCommand(
865 name = "buildinfo",
866 description = "Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
867 command = "make -j1 buildinfo V=s || true",
868 env = MakeEnv(),
869 haltOnFailure = True
870 ))
871
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",
876 env = MakeEnv(),
877 haltOnFailure = True
878 ))
879
880 factory.addStep(ShellCommand(
881 name = "checksums",
882 description = "Calculating checksums",
883 command=["make", "-j1", "checksum", "V=s"],
884 env = MakeEnv(),
885 haltOnFailure = True
886 ))
887
888 factory.addStep(ShellCommand(
889 name = "kmoddir",
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,
894 ))
895
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,
904 ))
905
906 factory.addStep(ShellCommand(
907 name = "kmodindex",
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])],
911 env = MakeEnv(),
912 haltOnFailure = True,
913 doStepIf = IsKmodArchiveEnabled,
914 ))
915
916 # sign
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,
923
924 ))
925
926 factory.addStep(ShellCommand(
927 name = "signpack",
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,
932 ))
933
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,
939 ))
940
941 factory.addStep(MasterShellCommand(
942 name = "signfiles",
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,
948 ))
949
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,
956 ))
957
958 factory.addStep(ShellCommand(
959 name = "signunpack",
960 description = "Unpacking signed files",
961 command = ["tar", "-xzf", "sign.tar.gz"],
962 haltOnFailure = True,
963 doStepIf = IsSignEnabled,
964 ))
965
966 # upload
967 factory.addStep(ShellCommand(
968 name = "dirprepare",
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)],
971 haltOnFailure = True
972 ))
973
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,
979 haltOnFailure = True
980 ))
981
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,
988 ))
989
990 factory.addStep(ShellCommand(
991 name = "dirupload",
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,
996 logEnviron = False,
997 locks = NetLockUl,
998 ))
999
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")) },
1006 logEnviron = False,
1007 haltOnFailure = False,
1008 flunkOnFailure = False,
1009 warnOnFailure = False,
1010 ))
1011
1012 # build list of files to upload
1013 factory.addStep(FileDownload(
1014 name = "dlsha2rsyncpl",
1015 mastersrc = scripts_dir + '/sha2rsync.pl',
1016 workerdest = "../sha2rsync.pl",
1017 mode = 0o755,
1018 ))
1019
1020 factory.addStep(ShellCommand(
1021 name = "buildlist",
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,
1025 ))
1026
1027 factory.addStep(FileDownload(
1028 name = "dlrsync.sh",
1029 mastersrc = scripts_dir + '/rsync.sh',
1030 workerdest = "../rsync.sh",
1031 mode = 0o755
1032 ))
1033
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,
1043 logEnviron = False,
1044 ))
1045
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,
1055 logEnviron = False,
1056 locks = NetLockUl,
1057 ))
1058
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,
1067 logEnviron = False,
1068 locks = NetLockUl,
1069 doStepIf = IsKmodArchiveEnabled,
1070 ))
1071
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
1077 ))
1078
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,
1086 logEnviron = False,
1087 locks = NetLockUl,
1088 ))
1089
1090 factory.addStep(ShellCommand(
1091 name = "df",
1092 description = "Reporting disk usage",
1093 command=["df", "-h", "."],
1094 env={'LC_ALL': 'C'},
1095 haltOnFailure = False,
1096 flunkOnFailure = False,
1097 warnOnFailure = False,
1098 alwaysRun = True
1099 ))
1100
1101 factory.addStep(ShellCommand(
1102 name = "du",
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,
1109 alwaysRun = True
1110 ))
1111
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,
1121 alwaysRun = True,
1122 ))
1123
1124 c['builders'].append(BuilderConfig(name=target, workernames=workerNames, factory=factory, nextBuild=GetNextBuild))
1125
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)
1133 ))
1134
1135
1136 ####### STATUS TARGETS
1137
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.
1141
1142 if "status_bind" in inip1:
1143 c['www'] = {
1144 'port': inip1.get("status_bind"),
1145 'plugins': {
1146 'waterfall_view': True,
1147 'console_view': True,
1148 'grid_view': True
1149 }
1150 }
1151
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"))
1155 ])
1156 c['www']['authz'] = util.Authz(
1157 allowRules=[ util.AnyControlEndpointMatcher(role="admins") ],
1158 roleMatchers=[ util.RolesFromUsername(roles=["admins"], usernames=[inip1.get("status_user")]) ]
1159 )
1160
1161 c['services'] = []
1162 if ini.has_section("irc"):
1163 iniirc = ini['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)
1169
1170 if irc_host and irc_nick and irc_chan:
1171 irc = reporters.IRC(irc_host, irc_nick,
1172 port = irc_port,
1173 password = irc_pass,
1174 channels = [ irc_chan ],
1175 notify_events = [ 'exception', 'problem', 'recovery' ]
1176 )
1177
1178 c['services'].append(irc)
1179
1180 c['revlink'] = util.RevlinkMatch([
1181 r'https://git.openwrt.org/openwrt/(.*).git'
1182 ],
1183 r'https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s')
1184
1185 ####### DB URL
1186
1187 c['db'] = {
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",
1191 }
1192
1193 c['buildbotNetUsageData'] = None