9453958d276d61ae351ec9fd26ea3baecda44a1a
[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 branchNames = [branches[b]["name"] for b in branches]
248
249 # find targets
250 targets = set()
251
252 def populateTargets():
253 log.msg("Populating targets, this will take time")
254 sourcegit = work_dir + '/source.git'
255 for branch in branchNames:
256 if os.path.isdir(sourcegit):
257 subprocess.call(["rm", "-rf", sourcegit])
258
259 subprocess.call(["git", "clone", "--depth=1", "--branch="+branch, repo_url, sourcegit])
260
261 os.makedirs(sourcegit + '/tmp', exist_ok=True)
262 findtargets = subprocess.Popen(['./scripts/dump-target-info.pl', 'targets'],
263 stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, cwd = sourcegit)
264
265 while True:
266 line = findtargets.stdout.readline()
267 if not line:
268 break
269 ta = line.decode().strip().split(' ')
270 targets.add(ta[0])
271
272 subprocess.call(["rm", "-rf", sourcegit])
273
274 populateTargets()
275
276 # the 'change_source' setting tells the buildmaster how it should find out
277 # about source code changes. Here we point to the buildbot clone of pyflakes.
278
279 c['change_source'] = []
280 c['change_source'].append(GitPoller(
281 repo_url,
282 workdir=work_dir+'/work.git', branch=repo_branch,
283 pollinterval=300))
284
285 ####### SCHEDULERS
286
287 # Configure the Schedulers, which decide how to react to incoming changes. In this
288 # case, just kick off a 'basebuild' build
289
290 class TagChoiceParameter(BaseParameter):
291 spec_attributes = ["strict", "choices"]
292 type = "list"
293 strict = True
294
295 def __init__(self, name, label=None, **kw):
296 super().__init__(name, label, **kw)
297 self._choice_list = []
298
299 @property
300 def choices(self):
301 taglist = []
302 basever = re.search(r'-([0-9]+\.[0-9]+)$', repo_branch)
303
304 if basever:
305 findtags = subprocess.Popen(
306 ['git', 'ls-remote', '--tags', repo_url],
307 stdout = subprocess.PIPE)
308
309 while True:
310 line = findtags.stdout.readline()
311
312 if not line:
313 break
314
315 tagver = re.search(r'\brefs/tags/v([0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?)$', line.decode().strip())
316
317 if tagver and tagver[1].find(basever[1]) == 0:
318 taglist.append(tagver[1])
319
320 taglist.sort(reverse=True, key=lambda tag: tag if re.search(r'-rc[0-9]+$', tag) else tag + '-z')
321 taglist.insert(0, '')
322
323 self._choice_list = taglist
324
325 return self._choice_list
326
327 def parse_from_arg(self, s):
328 if self.strict and s not in self._choice_list:
329 raise ValidationError("'%s' does not belong to list of available choices '%s'" % (s, self._choice_list))
330 return s
331
332 c['schedulers'] = []
333 c['schedulers'].append(SingleBranchScheduler(
334 name = "all",
335 change_filter = filter.ChangeFilter(branch=repo_branch),
336 treeStableTimer = 60,
337 builderNames = list(targets)))
338
339 c['schedulers'].append(ForceScheduler(
340 name = "force",
341 buttonName = "Force builds",
342 label = "Force build details",
343 builderNames = [ "00_force_build" ],
344
345 codebases = [
346 util.CodebaseParameter(
347 "",
348 label = "Repository",
349 branch = util.FixedParameter(name = "branch", default = ""),
350 revision = util.FixedParameter(name = "revision", default = ""),
351 repository = util.FixedParameter(name = "repository", default = ""),
352 project = util.FixedParameter(name = "project", default = "")
353 )
354 ],
355
356 reason = util.StringParameter(
357 name = "reason",
358 label = "Reason",
359 default = "Trigger build",
360 required = True,
361 size = 80
362 ),
363
364 properties = [
365 util.NestedParameter(
366 name="options",
367 label="Build Options",
368 layout="vertical",
369 fields=[
370 util.ChoiceStringParameter(
371 name = "target",
372 label = "Build target",
373 default = "all",
374 choices = set( "all" ) | targets
375 ),
376 TagChoiceParameter(
377 name = "tag",
378 label = "Build tag",
379 default = ""
380 )
381 ]
382 )
383 ]
384 ))
385
386 ####### BUILDERS
387
388 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
389 # what steps, and which workers can execute them. Note that any particular build will
390 # only take place on one worker.
391
392 def IsTaggingRequested(step):
393 tag = step.getProperty("tag")
394 return tag and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", tag)
395
396 def IsNoMasterBuild(step):
397 return step.getProperty("branch") != "master"
398
399 def IsUsignEnabled(step):
400 branch = step.getProperty("branch")
401 return branch and branches[branch].get("usign_key")
402
403 def IsSignEnabled(step):
404 branch = step.getProperty("branch")
405 return IsUsignEnabled(step) or branch and branches[branch].get("gpg_key")
406
407 def IsKmodArchiveEnabled(step):
408 branch = step.getProperty("branch")
409 return branch and branches[branch].get("kmod_archive")
410
411 def GetBaseVersion(branch):
412 if re.match(r"^[^-]+-[0-9]+\.[0-9]+$", branch):
413 return branch.split('-')[1]
414 else:
415 return "master"
416
417 @properties.renderer
418 def GetVersionPrefix(props):
419 branch = props.getProperty("branch")
420 basever = GetBaseVersion(branch)
421 if props.hasProperty("tag") and re.match(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-rc[0-9]+)?$", props["tag"]):
422 return "%s/" % props["tag"]
423 elif basever != "master":
424 return "%s-SNAPSHOT/" % basever
425 else:
426 return ""
427
428 @util.renderer
429 def GetConfigSeed(props):
430 branch = props.getProperty("branch")
431 return branch and branches[branch].get("config_seed") or ""
432
433 @util.renderer
434 def GetRsyncParams(props, srcorbin, urlorkey):
435 # srcorbin: 'bin' or 'src'; urlorkey: 'url' or 'key'
436 branch = props.getProperty("branch")
437 opt = srcorbin + "_" + urlorkey
438 return branch and branches[branch].get(opt)
439
440 @util.renderer
441 def GetUsignKey(props):
442 branch = props.getProperty("branch")
443 return branch and branches[branch].get("usign_key")
444
445 def GetNextBuild(builder, requests):
446 for r in requests:
447 if r.properties:
448 # order tagged build first
449 if r.properties.hasProperty("tag"):
450 return r
451 # then order by branch order
452 pbranch = r.properties.getProperty("branch")
453 for name in branchNames:
454 if pbranch == name:
455 return r
456
457 r = requests[0]
458 log.msg("GetNextBuild: {:>20} id: {} bsid: {}".format(builder.name, r.id, r.bsid))
459 return r
460
461 def MakeEnv(overrides=None, tryccache=False):
462 env = {
463 'CCC': Interpolate("%(prop:cc_command:-gcc)s"),
464 'CCXX': Interpolate("%(prop:cxx_command:-g++)s"),
465 }
466 if tryccache:
467 env['CC'] = Interpolate("%(prop:builddir)s/ccache_cc.sh")
468 env['CXX'] = Interpolate("%(prop:builddir)s/ccache_cxx.sh")
469 env['CCACHE'] = Interpolate("%(prop:ccache_command:-)s")
470 else:
471 env['CC'] = env['CCC']
472 env['CXX'] = env['CCXX']
473 env['CCACHE'] = ''
474 if overrides is not None:
475 env.update(overrides)
476 return env
477
478 @properties.renderer
479 def NetLockDl(props):
480 lock = None
481 if props.hasProperty("dl_lock"):
482 lock = NetLocks[props["dl_lock"]]
483 if lock is not None:
484 return [lock.access('exclusive')]
485 else:
486 return []
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
726 factory.addStep(ShellCommand(
727 name = "checkarch",
728 description = "Checking architecture",
729 command = ["grep", "-sq", "CONFIG_TARGET_%s=y" %(ts[0]), ".config"],
730 logEnviron = False,
731 want_stdout = False,
732 want_stderr = False,
733 haltOnFailure = True
734 ))
735
736 # find libc suffix
737 factory.addStep(SetPropertyFromCommand(
738 name = "libc",
739 property = "libc",
740 description = "Finding libc suffix",
741 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
742
743 # install build key
744 factory.addStep(StringDownload(
745 name = "dlkeybuildpub",
746 s = Interpolate("%(kw:sec2pub)s", sec2pub=UsignSec2Pub),
747 workerdest = "key-build.pub",
748 mode = 0o600,
749 doStepIf = IsUsignEnabled,
750 ))
751
752 factory.addStep(StringDownload(
753 name = "dlkeybuild",
754 s = "# fake private key",
755 workerdest = "key-build",
756 mode = 0o600,
757 doStepIf = IsUsignEnabled,
758 ))
759
760 factory.addStep(StringDownload(
761 name = "dlkeybuilducert",
762 s = "# fake certificate",
763 workerdest = "key-build.ucert",
764 mode = 0o600,
765 doStepIf = IsUsignEnabled,
766 ))
767
768 # prepare dl
769 factory.addStep(ShellCommand(
770 name = "dldir",
771 description = "Preparing dl/",
772 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
773 logEnviron = False,
774 want_stdout = False
775 ))
776
777 # prepare tar
778 factory.addStep(ShellCommand(
779 name = "dltar",
780 description = "Building and installing GNU tar",
781 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/tar/compile", "V=s"],
782 env = MakeEnv(tryccache=True),
783 haltOnFailure = True
784 ))
785
786 # populate dl
787 factory.addStep(ShellCommand(
788 name = "dlrun",
789 description = "Populating dl/",
790 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "download", "V=s"],
791 env = MakeEnv(),
792 logEnviron = False,
793 locks = properties.FlattenList(NetLockDl, [dlLock.access('exclusive')]),
794 ))
795
796 factory.addStep(ShellCommand(
797 name = "cleanbase",
798 description = "Cleaning base-files",
799 command=["make", "package/base-files/clean", "V=s"]
800 ))
801
802 # build
803 factory.addStep(ShellCommand(
804 name = "tools",
805 description = "Building and installing tools",
806 command = ["make", Interpolate("-j%(prop:nproc:-1)s"), "tools/install", "V=s"],
807 env = MakeEnv(tryccache=True),
808 haltOnFailure = True
809 ))
810
811 factory.addStep(ShellCommand(
812 name = "toolchain",
813 description = "Building and installing toolchain",
814 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "toolchain/install", "V=s"],
815 env = MakeEnv(),
816 haltOnFailure = True
817 ))
818
819 factory.addStep(ShellCommand(
820 name = "kmods",
821 description = "Building kmods",
822 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
823 env = MakeEnv(),
824 haltOnFailure = True
825 ))
826
827 # find kernel version
828 factory.addStep(SetPropertyFromCommand(
829 name = "kernelversion",
830 property = "kernelversion",
831 description = "Finding the effective Kernel version",
832 command = "make --no-print-directory -C target/linux/ val.LINUX_VERSION val.LINUX_RELEASE val.LINUX_VERMAGIC | xargs printf '%s-%s-%s\\n'",
833 env = { 'TOPDIR': Interpolate("%(prop:builddir)s/build") }
834 ))
835
836 factory.addStep(ShellCommand(
837 name = "pkgclean",
838 description = "Cleaning up package build",
839 command=["make", "package/cleanup", "V=s"]
840 ))
841
842 factory.addStep(ShellCommand(
843 name = "pkgbuild",
844 description = "Building packages",
845 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
846 env = MakeEnv(),
847 haltOnFailure = True
848 ))
849
850 factory.addStep(ShellCommand(
851 name = "pkginstall",
852 description = "Installing packages",
853 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/install", "V=s"],
854 env = MakeEnv(),
855 haltOnFailure = True
856 ))
857
858 factory.addStep(ShellCommand(
859 name = "pkgindex",
860 description = "Indexing packages",
861 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES="],
862 env = MakeEnv(),
863 haltOnFailure = True
864 ))
865
866 factory.addStep(ShellCommand(
867 name = "images",
868 description = "Building and installing images",
869 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "target/install", "V=s"],
870 env = MakeEnv(),
871 haltOnFailure = True
872 ))
873
874 factory.addStep(ShellCommand(
875 name = "buildinfo",
876 description = "Generating config.buildinfo, version.buildinfo and feeds.buildinfo",
877 command = "make -j1 buildinfo V=s || true",
878 env = MakeEnv(),
879 haltOnFailure = True
880 ))
881
882 factory.addStep(ShellCommand(
883 name = "json_overview_image_info",
884 description = "Generate profiles.json in target folder",
885 command = "make -j1 json_overview_image_info V=s || true",
886 env = MakeEnv(),
887 haltOnFailure = True
888 ))
889
890 factory.addStep(ShellCommand(
891 name = "checksums",
892 description = "Calculating checksums",
893 command=["make", "-j1", "checksum", "V=s"],
894 env = MakeEnv(),
895 haltOnFailure = True
896 ))
897
898 factory.addStep(ShellCommand(
899 name = "kmoddir",
900 description = "Creating kmod directory",
901 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])],
902 haltOnFailure = True,
903 doStepIf = IsKmodArchiveEnabled,
904 ))
905
906 factory.addStep(ShellCommand(
907 name = "kmodprepare",
908 description = "Preparing kmod archive",
909 command=["rsync", "--include=/kmod-*.ipk", "--exclude=*", "-va",
910 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/packages/", target=ts[0], subtarget=ts[1]),
911 Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
912 haltOnFailure = True,
913 doStepIf = IsKmodArchiveEnabled,
914 ))
915
916 factory.addStep(ShellCommand(
917 name = "kmodindex",
918 description = "Indexing kmod archive",
919 command=["make", Interpolate("-j%(prop:nproc:-1)s"), "package/index", "V=s", "CONFIG_SIGNED_PACKAGES=",
920 Interpolate("PACKAGE_SUBDIRS=bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1])],
921 env = MakeEnv(),
922 haltOnFailure = True,
923 doStepIf = IsKmodArchiveEnabled,
924 ))
925
926 # sign
927 factory.addStep(MasterShellCommand(
928 name = "signprepare",
929 description = "Preparing temporary signing directory",
930 command = ["mkdir", "-p", "%s/signing" %(work_dir)],
931 haltOnFailure = True,
932 doStepIf = IsSignEnabled,
933
934 ))
935
936 factory.addStep(ShellCommand(
937 name = "signpack",
938 description = "Packing files to sign",
939 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]),
940 haltOnFailure = True,
941 doStepIf = IsSignEnabled,
942 ))
943
944 factory.addStep(FileUpload(
945 workersrc = "sign.tar.gz",
946 masterdest = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
947 haltOnFailure = True,
948 doStepIf = IsSignEnabled,
949 ))
950
951 factory.addStep(MasterShellCommand(
952 name = "signfiles",
953 description = "Signing files",
954 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1])],
955 env = { 'CONFIG_INI': os.getenv("BUILDMASTER_CONFIG", "./config.ini") },
956 haltOnFailure = True,
957 doStepIf = IsSignEnabled,
958 ))
959
960 factory.addStep(FileDownload(
961 name = "dlsigntargz",
962 mastersrc = "%s/signing/%s.%s.tar.gz" %(work_dir, ts[0], ts[1]),
963 workerdest = "sign.tar.gz",
964 haltOnFailure = True,
965 doStepIf = IsSignEnabled,
966 ))
967
968 factory.addStep(ShellCommand(
969 name = "signunpack",
970 description = "Unpacking signed files",
971 command = ["tar", "-xzf", "sign.tar.gz"],
972 haltOnFailure = True,
973 doStepIf = IsSignEnabled,
974 ))
975
976 # upload
977 factory.addStep(ShellCommand(
978 name = "dirprepare",
979 description = "Preparing upload directory structure",
980 command = ["mkdir", "-p", Interpolate("tmp/upload/%(kw:prefix)stargets/%(kw:target)s/%(kw:subtarget)s", target=ts[0], subtarget=ts[1], prefix=GetVersionPrefix)],
981 haltOnFailure = True
982 ))
983
984 factory.addStep(ShellCommand(
985 name = "linkprepare",
986 description = "Preparing repository symlink",
987 command = ["ln", "-s", "-f", Interpolate("../packages-%(kw:basever)s", basever=util.Transform(GetBaseVersion, Property("branch"))), Interpolate("tmp/upload/%(kw:prefix)spackages", prefix=GetVersionPrefix)],
988 doStepIf = IsNoMasterBuild,
989 haltOnFailure = True
990 ))
991
992 factory.addStep(ShellCommand(
993 name = "kmoddirprepare",
994 description = "Preparing kmod archive upload directory",
995 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)],
996 haltOnFailure = True,
997 doStepIf = IsKmodArchiveEnabled,
998 ))
999
1000 factory.addStep(ShellCommand(
1001 name = "dirupload",
1002 description = "Uploading directory structure",
1003 command = ["rsync", "-az"] + rsync_defopts + ["tmp/upload/", Interpolate("%(kw:url)s/", url=GetRsyncParams.withArgs("bin", "url"))],
1004 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1005 haltOnFailure = True,
1006 logEnviron = False,
1007 locks = NetLockUl,
1008 ))
1009
1010 # download remote sha256sums to 'target-sha256sums'
1011 factory.addStep(ShellCommand(
1012 name = "target-sha256sums",
1013 description = "Fetching remote sha256sums for target",
1014 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"],
1015 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1016 logEnviron = False,
1017 haltOnFailure = False,
1018 flunkOnFailure = False,
1019 warnOnFailure = False,
1020 ))
1021
1022 # build list of files to upload
1023 factory.addStep(FileDownload(
1024 name = "dlsha2rsyncpl",
1025 mastersrc = scripts_dir + '/sha2rsync.pl',
1026 workerdest = "../sha2rsync.pl",
1027 mode = 0o755,
1028 ))
1029
1030 factory.addStep(ShellCommand(
1031 name = "buildlist",
1032 description = "Building list of files to upload",
1033 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"],
1034 haltOnFailure = True,
1035 ))
1036
1037 factory.addStep(FileDownload(
1038 name = "dlrsync.sh",
1039 mastersrc = scripts_dir + '/rsync.sh',
1040 workerdest = "../rsync.sh",
1041 mode = 0o755
1042 ))
1043
1044 # upload new files and update existing ones
1045 factory.addStep(ShellCommand(
1046 name = "targetupload",
1047 description = "Uploading target files",
1048 command=["../rsync.sh", "--exclude=/kmods/", "--files-from=rsynclist", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1049 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1050 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)],
1051 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1052 haltOnFailure = True,
1053 logEnviron = False,
1054 ))
1055
1056 # delete files which don't exist locally
1057 factory.addStep(ShellCommand(
1058 name = "targetprune",
1059 description = "Pruning target files",
1060 command=["../rsync.sh", "--exclude=/kmods/", "--delete", "--existing", "--ignore-existing", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1061 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/", target=ts[0], subtarget=ts[1]),
1062 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)],
1063 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1064 haltOnFailure = True,
1065 logEnviron = False,
1066 locks = NetLockUl,
1067 ))
1068
1069 factory.addStep(ShellCommand(
1070 name = "kmodupload",
1071 description = "Uploading kmod archive",
1072 command=["../rsync.sh", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1])] + rsync_defopts +
1073 ["-a", Interpolate("bin/targets/%(kw:target)s/%(kw:subtarget)s%(prop:libc)s/kmods/%(prop:kernelversion)s/", target=ts[0], subtarget=ts[1]),
1074 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)],
1075 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("bin", "key")) },
1076 haltOnFailure = True,
1077 logEnviron = False,
1078 locks = NetLockUl,
1079 doStepIf = IsKmodArchiveEnabled,
1080 ))
1081
1082 factory.addStep(ShellCommand(
1083 name = "sourcelist",
1084 description = "Finding source archives to upload",
1085 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -not -name '*.hash' -not -name '*.dl' -newer .config -printf '%f\\n' > sourcelist",
1086 haltOnFailure = True
1087 ))
1088
1089 factory.addStep(ShellCommand(
1090 name = "sourceupload",
1091 description = "Uploading source archives",
1092 command=["../rsync.sh", "--files-from=sourcelist", "--size-only", "--delay-updates"] + rsync_defopts +
1093 [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"))],
1094 env={ 'RSYNC_PASSWORD': Interpolate("%(kw:key)s", key=GetRsyncParams.withArgs("src", "key")) },
1095 haltOnFailure = True,
1096 logEnviron = False,
1097 locks = NetLockUl,
1098 ))
1099
1100 factory.addStep(ShellCommand(
1101 name = "df",
1102 description = "Reporting disk usage",
1103 command=["df", "-h", "."],
1104 env={'LC_ALL': 'C'},
1105 haltOnFailure = False,
1106 flunkOnFailure = False,
1107 warnOnFailure = False,
1108 alwaysRun = True
1109 ))
1110
1111 factory.addStep(ShellCommand(
1112 name = "du",
1113 description = "Reporting estimated file space usage",
1114 command=["du", "-sh", "."],
1115 env={'LC_ALL': 'C'},
1116 haltOnFailure = False,
1117 flunkOnFailure = False,
1118 warnOnFailure = False,
1119 alwaysRun = True
1120 ))
1121
1122 factory.addStep(ShellCommand(
1123 name = "ccachestat",
1124 description = "Reporting ccache stats",
1125 command=["ccache", "-s"],
1126 env = MakeEnv(overrides={ 'PATH': ["${PATH}", "./staging_dir/host/bin"] }),
1127 want_stderr = False,
1128 haltOnFailure = False,
1129 flunkOnFailure = False,
1130 warnOnFailure = False,
1131 alwaysRun = True,
1132 ))
1133
1134 c['builders'].append(BuilderConfig(name=target, workernames=workerNames, factory=factory, nextBuild=GetNextBuild))
1135
1136 c['schedulers'].append(schedulers.Triggerable(name="trigger_%s" % target, builderNames=[ target ]))
1137 force_factory.addStep(steps.Trigger(
1138 name = "trigger_%s" % target,
1139 description = "Triggering %s build" % target,
1140 schedulerNames = [ "trigger_%s" % target ],
1141 set_properties = { "reason": Property("reason"), "tag": TagPropertyValue },
1142 doStepIf = IsTargetSelected(target)
1143 ))
1144
1145
1146 ####### STATUS TARGETS
1147
1148 # 'status' is a list of Status Targets. The results of each build will be
1149 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
1150 # including web pages, email senders, and IRC bots.
1151
1152 if "status_bind" in inip1:
1153 c['www'] = {
1154 'port': inip1.get("status_bind"),
1155 'plugins': {
1156 'waterfall_view': True,
1157 'console_view': True,
1158 'grid_view': True
1159 }
1160 }
1161
1162 if "status_user" in inip1 and "status_password" in inip1:
1163 c['www']['auth'] = util.UserPasswordAuth([
1164 (inip1.get("status_user"), inip1.get("status_password"))
1165 ])
1166 c['www']['authz'] = util.Authz(
1167 allowRules=[ util.AnyControlEndpointMatcher(role="admins") ],
1168 roleMatchers=[ util.RolesFromUsername(roles=["admins"], usernames=[inip1.get("status_user")]) ]
1169 )
1170
1171 c['services'] = []
1172 if ini.has_section("irc"):
1173 iniirc = ini['irc']
1174 irc_host = iniirc.get("host", None)
1175 irc_port = iniirc.getint("port", 6667)
1176 irc_chan = iniirc.get("channel", None)
1177 irc_nick = iniirc.get("nickname", None)
1178 irc_pass = iniirc.get("password", None)
1179
1180 if irc_host and irc_nick and irc_chan:
1181 irc = reporters.IRC(irc_host, irc_nick,
1182 port = irc_port,
1183 password = irc_pass,
1184 channels = [ irc_chan ],
1185 notify_events = [ 'exception', 'problem', 'recovery' ]
1186 )
1187
1188 c['services'].append(irc)
1189
1190 c['revlink'] = util.RevlinkMatch([
1191 r'https://git.openwrt.org/openwrt/(.*).git'
1192 ],
1193 r'https://git.openwrt.org/?p=openwrt/\1.git;a=commit;h=%s')
1194
1195 ####### DB URL
1196
1197 c['db'] = {
1198 # This specifies what database buildbot uses to store its state. You can leave
1199 # this at its default for all but the largest installations.
1200 'db_url' : "sqlite:///state.sqlite",
1201 }
1202
1203 c['buildbotNetUsageData'] = None