2 # ex: set syntax=python:
9 from buildbot import locks
11 # This is a sample buildmaster config file. It must be installed as
12 # 'master.cfg' in your buildmaster's base directory.
14 ini = ConfigParser.ConfigParser()
15 ini.read("./config.ini")
17 # This is the dictionary that the buildmaster pays attention to. We also use
18 # a shorter alias to save typing.
19 c = BuildmasterConfig = {}
23 # The 'slaves' list defines the set of recognized buildslaves. Each element is
24 # a BuildSlave object, specifying a unique slave name and password. The same
25 # slave name and password must be configured on the slave.
26 from buildbot.buildslave import BuildSlave
30 if ini.has_option("general", "port"):
31 slave_port = ini.getint("general", "port")
36 for section in ini.sections():
37 if section.startswith("slave "):
38 if ini.has_option(section, "name") and ini.has_option(section, "password"):
39 name = ini.get(section, "name")
40 password = ini.get(section, "password")
42 if ini.has_option(section, "builds"):
43 max_builds[name] = ini.getint(section, "builds")
44 c['slaves'].append(BuildSlave(name, password, max_builds = max_builds[name]))
46 # 'slavePortnum' defines the TCP port to listen on for connections from slaves.
47 # This must match the value configured into the buildslaves (with their
49 c['slavePortnum'] = slave_port
52 c['mergeRequests'] = True
54 # Reduce amount of backlog data
55 c['buildHorizon'] = 30
60 home_dir = os.path.abspath(ini.get("general", "homedir"))
68 if ini.has_option("general", "expire"):
69 tree_expire = ini.getint("general", "expire")
71 if ini.has_option("general", "other_builds"):
72 other_builds = ini.getint("general", "other_builds")
74 if ini.has_option("general", "cc_version"):
75 cc_version = ini.get("general", "cc_version").split()
76 if len(cc_version) == 1:
77 cc_version = ["eq", cc_version[0]]
79 repo_url = ini.get("repo", "url")
80 repo_branch = "master"
82 if ini.has_option("repo", "branch"):
83 repo_branch = ini.get("repo", "branch")
85 rsync_bin_url = ini.get("rsync", "binary_url")
86 rsync_bin_key = ini.get("rsync", "binary_password")
91 if ini.has_option("rsync", "source_url"):
92 rsync_src_url = ini.get("rsync", "source_url")
93 rsync_src_key = ini.get("rsync", "source_password")
97 gpg_comment = "Unattended build signature"
98 gpg_passfile = "/dev/null"
100 if ini.has_option("gpg", "home"):
101 gpg_home = ini.get("gpg", "home")
103 if ini.has_option("gpg", "keyid"):
104 gpg_keyid = ini.get("gpg", "keyid")
106 if ini.has_option("gpg", "comment"):
107 gpg_comment = ini.get("gpg", "comment")
109 if ini.has_option("gpg", "passfile"):
110 gpg_passfile = ini.get("gpg", "passfile")
116 if not os.path.isdir(home_dir+'/source.git'):
117 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, home_dir+'/source.git'])
119 subprocess.call(["git", "pull"], cwd = home_dir+'/source.git')
121 findtargets = subprocess.Popen([home_dir+'/dumpinfo.pl', 'targets'],
122 stdout = subprocess.PIPE, cwd = home_dir+'/source.git')
125 line = findtargets.stdout.readline()
128 ta = line.strip().split(' ')
129 targets.append(ta[0])
132 # the 'change_source' setting tells the buildmaster how it should find out
133 # about source code changes. Here we point to the buildbot clone of pyflakes.
135 from buildbot.changes.gitpoller import GitPoller
136 c['change_source'] = []
137 c['change_source'].append(GitPoller(
139 workdir=home_dir+'/work.git', branch=repo_branch,
144 # Configure the Schedulers, which decide how to react to incoming changes. In this
145 # case, just kick off a 'basebuild' build
147 from buildbot.schedulers.basic import SingleBranchScheduler
148 from buildbot.schedulers.forcesched import ForceScheduler
149 from buildbot.changes import filter
151 c['schedulers'].append(SingleBranchScheduler(
153 change_filter=filter.ChangeFilter(branch=repo_branch),
155 builderNames=targets))
157 c['schedulers'].append(ForceScheduler(
159 builderNames=targets))
163 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
164 # what steps, and which slaves can execute them. Note that any particular build will
165 # only take place on one slave.
167 from buildbot.process.factory import BuildFactory
168 from buildbot.steps.source.git import Git
169 from buildbot.steps.shell import ShellCommand
170 from buildbot.steps.shell import SetProperty
171 from buildbot.steps.transfer import FileUpload
172 from buildbot.steps.transfer import FileDownload
173 from buildbot.steps.master import MasterShellCommand
174 from buildbot.process.properties import WithProperties
178 [ "tools", "tools/clean" ],
179 [ "chain", "toolchain/clean" ],
180 [ "linux", "target/linux/clean" ],
181 [ "dir", "dirclean" ],
182 [ "dist", "distclean" ]
185 def IsCleanRequested(pattern):
186 def CheckCleanProperty(step):
187 val = step.getProperty("clean")
188 if val and re.match(pattern, val):
193 return CheckCleanProperty
195 def IsTaggingRequested(step):
196 val = step.getProperty("tag")
197 if val and re.match("^[0-9]+\.[0-9]+\.[0-9]+$", val):
202 def IsNoTaggingRequested(step):
203 return not IsTaggingRequested(step)
205 def IsNoMasterBuild(step):
206 return repo_branch != "master"
208 def GetBaseVersion(props):
209 if re.match("^[^-]+-[0-9]+\.[0-9]+$", repo_branch):
210 return repo_branch.split('-')[1]
214 def GetVersionPrefix(props):
215 basever = GetBaseVersion(props)
216 if props.hasProperty("tag") and re.match("^[0-9]+\.[0-9]+\.[0-9]+$", props["tag"]):
217 return "%s/" % props["tag"]
218 elif basever != "master":
219 return "%s-SNAPSHOT/" % basever
223 def GetNumJobs(props):
224 if props.hasProperty("slavename") and props.hasProperty("nproc"):
225 return ((int(props["nproc"]) / (max_builds[props["slavename"]] + other_builds)) + 1)
230 if props.hasProperty("cc_command"):
231 return props["cc_command"]
236 if props.hasProperty("cxx_command"):
237 return props["cxx_command"]
241 def MakeEnv(overrides=None):
243 'CC': WithProperties("%(cc)s", cc=GetCC),
244 'CXX': WithProperties("%(cxx)s", cxx=GetCXX)
246 if overrides is not None:
247 env.update(overrides)
253 dlLock = locks.SlaveLock("slave_dl")
254 tagLock = locks.MasterLock("make_tag")
256 checkBuiltin = re.sub('[\t\n ]+', ' ', """
258 local symbol op path file;
259 for file in $CHANGED_FILES; do
265 while read symbol op path; do
266 case "$symbol" in package-*)
267 symbol="${symbol##*(}";
268 symbol="${symbol%)}";
269 for file in $CHANGED_FILES; do
270 case "$file" in "package/$path/"*)
271 grep -qsx "$symbol=y" .config && return 0
275 done < tmp/.packagedeps;
281 class IfBuiltinShellCommand(ShellCommand):
282 def _quote(self, str):
283 if re.search("[^a-zA-Z0-9/_.-]", str):
284 return "'%s'" %(re.sub("'", "'\"'\"'", str))
287 def setCommand(self, command):
288 if not isinstance(command, (str, unicode)):
289 command = ' '.join(map(self._quote, command))
292 '%s; if checkBuiltin; then %s; else exit 0; fi' %(checkBuiltin, command)
295 def setupEnvironment(self, cmd):
296 slaveEnv = self.slaveEnvironment
300 for request in self.build.requests:
301 for source in request.sources:
302 for change in source.changes:
303 for file in change.files:
304 changedFiles[file] = True
305 fullSlaveEnv = slaveEnv.copy()
306 fullSlaveEnv['CHANGED_FILES'] = ' '.join(changedFiles.keys())
307 cmd.args['env'] = fullSlaveEnv
311 for slave in c['slaves']:
312 slaveNames.append(slave.slavename)
314 for target in targets:
315 ts = target.split('/')
317 factory = BuildFactory()
319 # find number of cores
320 factory.addStep(SetProperty(
323 description = "Finding number of CPUs",
324 command = ["nproc"]))
326 # find gcc and g++ compilers
327 if cc_version is not None:
328 factory.addStep(FileDownload(
329 mastersrc = "findbin.pl",
330 slavedest = "../findbin.pl",
333 factory.addStep(SetProperty(
335 property = "cc_command",
336 description = "Finding gcc command",
337 command = ["../findbin.pl", "gcc", cc_version[0], cc_version[1]],
338 haltOnFailure = True))
340 factory.addStep(SetProperty(
342 property = "cxx_command",
343 description = "Finding g++ command",
344 command = ["../findbin.pl", "g++", cc_version[0], cc_version[1]],
345 haltOnFailure = True))
347 # expire tree if needed
349 factory.addStep(FileDownload(
350 mastersrc = "expire.sh",
351 slavedest = "../expire.sh",
354 factory.addStep(ShellCommand(
356 description = "Checking for build tree expiry",
357 command = ["./expire.sh", str(tree_expire)],
359 haltOnFailure = True,
362 # user-requested clean targets
363 for tuple in CleanTargetMap:
364 factory.addStep(ShellCommand(
366 description = 'User-requested "make %s"' % tuple[1],
367 command = ["make", tuple[1], "V=s"],
369 doStepIf = IsCleanRequested(tuple[0])
372 factory.addStep(MasterShellCommand(
374 description = "Tagging Git repository",
375 command = [home_dir+'/maketag.sh', '-i', '-k', str(gpg_keyid or ''),
376 '-p', str(gpg_passfile or ''), '-v', WithProperties("%(tag:-)s")],
377 path = home_dir+'/source.git',
378 env = {'GNUPGHOME': gpg_home},
379 haltOnFailure = True,
380 doStepIf = IsTaggingRequested,
381 locks = [tagLock.access('exclusive')]
385 factory.addStep(ShellCommand(
386 name = "switchbranch",
387 description = "Checking out Git branch",
388 command = "if [ -d .git ]; then git checkout '%s'; else exit 0; fi" % repo_branch,
389 haltOnFailure = True,
390 doStepIf = IsNoTaggingRequested
393 # check out the source
396 branch = repo_branch,
397 mode = 'incremental',
401 factory.addStep(ShellCommand(
403 description = "Fetching Git remote refs",
404 command = ["git", "fetch", "origin", "+refs/heads/%s:refs/remotes/origin/%s" %(repo_branch, repo_branch)],
409 factory.addStep(ShellCommand(
411 description = "Fetching Git tags",
412 command = ["git", "fetch", "--tags", "--", repo_url],
413 haltOnFailure = True,
414 doStepIf = IsTaggingRequested
418 factory.addStep(ShellCommand(
420 description = "Checking out Git tag",
421 command = ["git", "checkout", WithProperties("tags/v%(tag:-)s")],
422 haltOnFailure = True,
423 doStepIf = IsTaggingRequested
426 factory.addStep(ShellCommand(
428 description = "Remove tmp folder",
429 command=["rm", "-rf", "tmp/"]))
432 # factory.addStep(ShellCommand(
433 # name = "feedsconf",
434 # description = "Copy the feeds.conf",
435 # command='''cp ~/feeds.conf ./feeds.conf''' ))
438 factory.addStep(ShellCommand(
439 name = "rmfeedlinks",
440 description = "Remove feed symlinks",
441 command=["rm", "-rf", "package/feeds/"]))
444 factory.addStep(ShellCommand(
445 name = "updatefeeds",
446 description = "Updating feeds",
447 command=["./scripts/feeds", "update"],
451 factory.addStep(ShellCommand(
452 name = "installfeeds",
453 description = "Installing feeds",
454 command=["./scripts/feeds", "install", "-a"],
458 factory.addStep(FileDownload(
459 mastersrc = "config.seed",
460 slavedest = ".config",
465 factory.addStep(ShellCommand(
467 description = "Seeding .config",
468 command = "printf 'CONFIG_TARGET_%s=y\\nCONFIG_TARGET_%s_%s=y\\n' >> .config" %(ts[0], ts[0], ts[1])
471 factory.addStep(ShellCommand(
473 description = "Removing output directory",
474 command = ["rm", "-rf", "bin/"]
477 factory.addStep(ShellCommand(
479 description = "Populating .config",
480 command = ["make", "defconfig"],
485 factory.addStep(ShellCommand(
487 description = "Checking architecture",
488 command = ["grep", "-sq", "CONFIG_TARGET_%s=y" %(ts[0]), ".config"],
496 factory.addStep(SetProperty(
499 description = "Finding libc suffix",
500 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
503 factory.addStep(FileDownload(mastersrc=home_dir+'/key-build', slavedest="key-build", mode=0600))
504 factory.addStep(FileDownload(mastersrc=home_dir+'/key-build.pub', slavedest="key-build.pub", mode=0600))
507 factory.addStep(ShellCommand(
509 description = "Preparing dl/",
510 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
516 factory.addStep(ShellCommand(
518 description = "Building GNU tar",
519 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "tools/tar/install", "V=s"],
525 factory.addStep(ShellCommand(
527 description = "Populating dl/",
528 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "download", "V=s"],
531 locks = [dlLock.access('exclusive')]
534 factory.addStep(ShellCommand(
536 description = "Cleaning base-files",
537 command=["make", "package/base-files/clean", "V=s"]
541 factory.addStep(ShellCommand(
543 description = "Building tools",
544 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "tools/install", "V=s"],
549 factory.addStep(ShellCommand(
551 description = "Building toolchain",
552 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "toolchain/install", "V=s"],
557 factory.addStep(ShellCommand(
559 description = "Building kmods",
560 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
562 #env={'BUILD_LOG_DIR': 'bin/%s' %(ts[0])},
566 factory.addStep(ShellCommand(
568 description = "Building packages",
569 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
571 #env={'BUILD_LOG_DIR': 'bin/%s' %(ts[0])},
575 # factory.addStep(IfBuiltinShellCommand(
576 factory.addStep(ShellCommand(
578 description = "Installing packages",
579 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/install", "V=s"],
584 factory.addStep(ShellCommand(
586 description = "Indexing packages",
587 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/index", "V=s"],
592 #factory.addStep(IfBuiltinShellCommand(
593 factory.addStep(ShellCommand(
595 description = "Building images",
596 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "target/install", "V=s"],
601 factory.addStep(ShellCommand(
603 description = "Generating config.seed",
604 command=["make", "-j1", "diffconfig", "V=s"],
609 factory.addStep(ShellCommand(
611 description = "Calculating checksums",
612 command=["make", "-j1", "checksum", "V=s"],
618 if gpg_keyid is not None:
619 factory.addStep(MasterShellCommand(
620 name = "signprepare",
621 description = "Preparing temporary signing directory",
622 command = ["mkdir", "-p", "%s/signing" %(home_dir)],
626 factory.addStep(ShellCommand(
628 description = "Packing files to sign",
629 command = WithProperties("find bin/targets/%s/%s%%(libc)s/ -mindepth 1 -maxdepth 2 -type f -name sha256sums -print0 -or -name Packages -print0 | xargs -0 tar -czf sign.tar.gz" %(ts[0], ts[1])),
633 factory.addStep(FileUpload(
634 slavesrc = "sign.tar.gz",
635 masterdest = "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]),
639 factory.addStep(MasterShellCommand(
641 description = "Signing files",
642 command = ["%s/signall.sh" %(home_dir), "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]), gpg_keyid, gpg_comment],
643 env = {'GNUPGHOME': gpg_home, 'PASSFILE': gpg_passfile},
647 factory.addStep(FileDownload(
648 mastersrc = "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]),
649 slavedest = "sign.tar.gz",
653 factory.addStep(ShellCommand(
655 description = "Unpacking signed files",
656 command = ["tar", "-xzf", "sign.tar.gz"],
661 factory.addStep(ShellCommand(
663 description = "Preparing upload directory structure",
664 command = ["mkdir", "-p", WithProperties("tmp/upload/%%(prefix)stargets/%s/%s" %(ts[0], ts[1]), prefix=GetVersionPrefix)],
668 factory.addStep(ShellCommand(
669 name = "linkprepare",
670 description = "Preparing repository symlink",
671 command = ["ln", "-s", "-f", WithProperties("../packages-%(basever)s", basever=GetBaseVersion), WithProperties("tmp/upload/%(prefix)spackages", prefix=GetVersionPrefix)],
672 doStepIf = IsNoMasterBuild,
676 factory.addStep(ShellCommand(
678 description = "Uploading directory structure",
679 command = ["rsync", "-avz", "tmp/upload/", "%s/" %(rsync_bin_url)],
680 env={'RSYNC_PASSWORD': rsync_bin_key},
681 haltOnFailure = True,
685 factory.addStep(ShellCommand(
686 name = "targetupload",
687 description = "Uploading target files",
688 command=["rsync", "--progress", "--delete", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]),
689 "-avz", WithProperties("bin/targets/%s/%s%%(libc)s/" %(ts[0], ts[1])),
690 WithProperties("%s/%%(prefix)stargets/%s/%s/" %(rsync_bin_url, ts[0], ts[1]), prefix=GetVersionPrefix)],
691 env={'RSYNC_PASSWORD': rsync_bin_key},
692 haltOnFailure = True,
696 if rsync_src_url is not None:
697 factory.addStep(ShellCommand(
698 name = "sourceupload",
699 description = "Uploading source archives",
700 command=["rsync", "--progress", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "dl/", "%s/" %(rsync_src_url)],
701 env={'RSYNC_PASSWORD': rsync_src_key},
702 haltOnFailure = True,
707 factory.addStep(ShellCommand(
708 name = "packageupload",
709 description = "Uploading package files",
710 command=["rsync", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "bin/packages/", "%s/packages/" %(rsync_bin_url)],
711 env={'RSYNC_PASSWORD': rsync_bin_key},
712 haltOnFailure = False,
718 factory.addStep(ShellCommand(
720 description = "Uploading logs",
721 command=["rsync", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "logs/", "%s/logs/%s/%s/" %(rsync_bin_url, ts[0], ts[1])],
722 env={'RSYNC_PASSWORD': rsync_bin_key},
723 haltOnFailure = False,
728 from buildbot.config import BuilderConfig
730 c['builders'].append(BuilderConfig(name=target, slavenames=slaveNames, factory=factory))
733 ####### STATUS TARGETS
735 # 'status' is a list of Status Targets. The results of each build will be
736 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
737 # including web pages, email senders, and IRC bots.
741 from buildbot.status import html
742 from buildbot.status.web import authz, auth
744 if ini.has_option("status", "bind"):
745 if ini.has_option("status", "user") and ini.has_option("status", "password"):
746 authz_cfg=authz.Authz(
747 # change any of these to True to enable; see the manual for more
749 auth=auth.BasicAuth([(ini.get("status", "user"), ini.get("status", "password"))]),
750 gracefulShutdown = 'auth',
751 forceBuild = 'auth', # use this to test your slave once it is set up
752 forceAllBuilds = 'auth',
755 stopAllBuilds = 'auth',
756 cancelPendingBuild = 'auth',
758 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind"), authz=authz_cfg))
760 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind")))
763 from buildbot.status import words
765 if ini.has_option("irc", "host") and ini.has_option("irc", "nickname") and ini.has_option("irc", "channel"):
766 irc_host = ini.get("irc", "host")
768 irc_chan = ini.get("irc", "channel")
769 irc_nick = ini.get("irc", "nickname")
772 if ini.has_option("irc", "port"):
773 irc_port = ini.getint("irc", "port")
775 if ini.has_option("irc", "password"):
776 irc_pass = ini.get("irc", "password")
778 irc = words.IRC(irc_host, irc_nick, port = irc_port, password = irc_pass,
779 channels = [{ "channel": irc_chan }],
782 'successToFailure': 1,
783 'failureToSuccess': 1
787 c['status'].append(irc)
790 ####### PROJECT IDENTITY
792 # the 'title' string will appear at the top of this buildbot
793 # installation's html.WebStatus home page (linked to the
794 # 'titleURL') and is embedded in the title of the waterfall HTML page.
796 c['title'] = ini.get("general", "title")
797 c['titleURL'] = ini.get("general", "title_url")
799 # the 'buildbotURL' string should point to the location where the buildbot's
800 # internal web server (usually the html.WebStatus page) is visible. This
801 # typically uses the port number set in the Waterfall 'status' entry, but
802 # with an externally-visible host name which the buildbot cannot figure out
805 c['buildbotURL'] = ini.get("general", "buildbot_url")
810 # This specifies what database buildbot uses to store its state. You can leave
811 # this at its default for all but the largest installations.
812 'db_url' : "sqlite:///state.sqlite",