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]+(?:-rc[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]+(?:-rc[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"]
242 if props.hasProperty("builddir"):
243 return props["builddir"]
244 elif props.hasProperty("workdir"):
245 return props["workdir"]
249 def GetNextBuild(builder, requests):
251 if r.properties and r.properties.hasProperty("tag"):
255 def MakeEnv(overrides=None):
257 'CC': WithProperties("%(cc)s", cc=GetCC),
258 'CXX': WithProperties("%(cxx)s", cxx=GetCXX),
259 'CCACHE_BASEDIR': WithProperties("%(cwd)s", cwd=GetCwd)
261 if overrides is not None:
262 env.update(overrides)
268 dlLock = locks.SlaveLock("slave_dl")
270 checkBuiltin = re.sub('[\t\n ]+', ' ', """
272 local symbol op path file;
273 for file in $CHANGED_FILES; do
279 while read symbol op path; do
280 case "$symbol" in package-*)
281 symbol="${symbol##*(}";
282 symbol="${symbol%)}";
283 for file in $CHANGED_FILES; do
284 case "$file" in "package/$path/"*)
285 grep -qsx "$symbol=y" .config && return 0
289 done < tmp/.packagedeps;
295 class IfBuiltinShellCommand(ShellCommand):
296 def _quote(self, str):
297 if re.search("[^a-zA-Z0-9/_.-]", str):
298 return "'%s'" %(re.sub("'", "'\"'\"'", str))
301 def setCommand(self, command):
302 if not isinstance(command, (str, unicode)):
303 command = ' '.join(map(self._quote, command))
306 '%s; if checkBuiltin; then %s; else exit 0; fi' %(checkBuiltin, command)
309 def setupEnvironment(self, cmd):
310 slaveEnv = self.slaveEnvironment
314 for request in self.build.requests:
315 for source in request.sources:
316 for change in source.changes:
317 for file in change.files:
318 changedFiles[file] = True
319 fullSlaveEnv = slaveEnv.copy()
320 fullSlaveEnv['CHANGED_FILES'] = ' '.join(changedFiles.keys())
321 cmd.args['env'] = fullSlaveEnv
325 for slave in c['slaves']:
326 slaveNames.append(slave.slavename)
328 for target in targets:
329 ts = target.split('/')
331 factory = BuildFactory()
333 # find number of cores
334 factory.addStep(SetProperty(
337 description = "Finding number of CPUs",
338 command = ["nproc"]))
340 # find gcc and g++ compilers
341 if cc_version is not None:
342 factory.addStep(FileDownload(
343 mastersrc = "findbin.pl",
344 slavedest = "../findbin.pl",
347 factory.addStep(SetProperty(
349 property = "cc_command",
350 description = "Finding gcc command",
351 command = ["../findbin.pl", "gcc", cc_version[0], cc_version[1]],
352 haltOnFailure = True))
354 factory.addStep(SetProperty(
356 property = "cxx_command",
357 description = "Finding g++ command",
358 command = ["../findbin.pl", "g++", cc_version[0], cc_version[1]],
359 haltOnFailure = True))
361 # expire tree if needed
363 factory.addStep(FileDownload(
364 mastersrc = "expire.sh",
365 slavedest = "../expire.sh",
368 factory.addStep(ShellCommand(
370 description = "Checking for build tree expiry",
371 command = ["./expire.sh", str(tree_expire)],
373 haltOnFailure = True,
376 # user-requested clean targets
377 for tuple in CleanTargetMap:
378 factory.addStep(ShellCommand(
380 description = 'User-requested "make %s"' % tuple[1],
381 command = ["make", tuple[1], "V=s"],
383 doStepIf = IsCleanRequested(tuple[0])
387 factory.addStep(ShellCommand(
388 name = "switchbranch",
389 description = "Checking out Git branch",
390 command = "if [ -d .git ]; then git fetch && git checkout '%s'; else exit 0; fi" % repo_branch,
391 haltOnFailure = True,
392 doStepIf = IsNoTaggingRequested
395 # check out the source
398 branch = repo_branch,
399 mode = 'incremental',
403 factory.addStep(ShellCommand(
405 description = "Fetching Git remote refs",
406 command = ["git", "fetch", "origin", "+refs/heads/%s:refs/remotes/origin/%s" %(repo_branch, repo_branch)],
411 factory.addStep(ShellCommand(
413 description = "Fetching Git tags",
414 command = ["git", "fetch", "--tags", "--", repo_url],
415 haltOnFailure = True,
416 doStepIf = IsTaggingRequested
420 factory.addStep(ShellCommand(
422 description = "Checking out Git tag",
423 command = ["git", "checkout", WithProperties("tags/v%(tag:-)s")],
424 haltOnFailure = True,
425 doStepIf = IsTaggingRequested
428 factory.addStep(ShellCommand(
430 description = "Remove tmp folder",
431 command=["rm", "-rf", "tmp/"]))
434 # factory.addStep(ShellCommand(
435 # name = "feedsconf",
436 # description = "Copy the feeds.conf",
437 # command='''cp ~/feeds.conf ./feeds.conf''' ))
440 factory.addStep(ShellCommand(
441 name = "rmfeedlinks",
442 description = "Remove feed symlinks",
443 command=["rm", "-rf", "package/feeds/"]))
446 factory.addStep(ShellCommand(
447 name = "updatefeeds",
448 description = "Updating feeds",
449 command=["./scripts/feeds", "update"],
453 factory.addStep(ShellCommand(
454 name = "installfeeds",
455 description = "Installing feeds",
456 command=["./scripts/feeds", "install", "-a"],
460 factory.addStep(FileDownload(
461 mastersrc = "config.seed",
462 slavedest = ".config",
467 factory.addStep(ShellCommand(
469 description = "Seeding .config",
470 command = "printf 'CONFIG_TARGET_%s=y\\nCONFIG_TARGET_%s_%s=y\\n' >> .config" %(ts[0], ts[0], ts[1])
473 factory.addStep(ShellCommand(
475 description = "Removing output directory",
476 command = ["rm", "-rf", "bin/"]
479 factory.addStep(ShellCommand(
481 description = "Populating .config",
482 command = ["make", "defconfig"],
487 factory.addStep(ShellCommand(
489 description = "Checking architecture",
490 command = ["grep", "-sq", "CONFIG_TARGET_%s=y" %(ts[0]), ".config"],
498 factory.addStep(SetProperty(
501 description = "Finding libc suffix",
502 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
505 factory.addStep(FileDownload(
506 mastersrc = "ccache.sh",
507 slavedest = "ccache.sh",
512 factory.addStep(ShellCommand(
514 description = "Preparing ccache",
515 command = ["./ccache.sh"]
519 factory.addStep(FileDownload(mastersrc=home_dir+'/key-build', slavedest="key-build", mode=0600))
520 factory.addStep(FileDownload(mastersrc=home_dir+'/key-build.pub', slavedest="key-build.pub", mode=0600))
523 factory.addStep(ShellCommand(
525 description = "Preparing dl/",
526 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
532 factory.addStep(ShellCommand(
534 description = "Building GNU tar",
535 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "tools/tar/compile", "V=s"],
541 factory.addStep(ShellCommand(
543 description = "Populating dl/",
544 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "download", "V=s"],
547 locks = [dlLock.access('exclusive')]
550 factory.addStep(ShellCommand(
552 description = "Cleaning base-files",
553 command=["make", "package/base-files/clean", "V=s"]
557 factory.addStep(ShellCommand(
559 description = "Building tools",
560 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "tools/install", "V=s"],
565 factory.addStep(ShellCommand(
567 description = "Building toolchain",
568 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "toolchain/install", "V=s"],
573 factory.addStep(ShellCommand(
575 description = "Building kmods",
576 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
578 #env={'BUILD_LOG_DIR': 'bin/%s' %(ts[0])},
582 factory.addStep(ShellCommand(
584 description = "Cleaning up package build",
585 command=["make", "package/cleanup", "V=s"]
588 factory.addStep(ShellCommand(
590 description = "Building packages",
591 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
593 #env={'BUILD_LOG_DIR': 'bin/%s' %(ts[0])},
597 # factory.addStep(IfBuiltinShellCommand(
598 factory.addStep(ShellCommand(
600 description = "Installing packages",
601 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/install", "V=s"],
606 factory.addStep(ShellCommand(
608 description = "Indexing packages",
609 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/index", "V=s"],
614 #factory.addStep(IfBuiltinShellCommand(
615 factory.addStep(ShellCommand(
617 description = "Building images",
618 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "target/install", "V=s"],
623 factory.addStep(ShellCommand(
625 description = "Generating config.seed",
626 command=["make", "-j1", "diffconfig", "V=s"],
631 factory.addStep(ShellCommand(
633 description = "Calculating checksums",
634 command=["make", "-j1", "checksum", "V=s"],
640 if gpg_keyid is not None:
641 factory.addStep(MasterShellCommand(
642 name = "signprepare",
643 description = "Preparing temporary signing directory",
644 command = ["mkdir", "-p", "%s/signing" %(home_dir)],
648 factory.addStep(ShellCommand(
650 description = "Packing files to sign",
651 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])),
655 factory.addStep(FileUpload(
656 slavesrc = "sign.tar.gz",
657 masterdest = "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]),
661 factory.addStep(MasterShellCommand(
663 description = "Signing files",
664 command = ["%s/signall.sh" %(home_dir), "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]), gpg_keyid, gpg_comment],
665 env = {'GNUPGHOME': gpg_home, 'PASSFILE': gpg_passfile},
669 factory.addStep(FileDownload(
670 mastersrc = "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]),
671 slavedest = "sign.tar.gz",
675 factory.addStep(ShellCommand(
677 description = "Unpacking signed files",
678 command = ["tar", "-xzf", "sign.tar.gz"],
683 factory.addStep(ShellCommand(
685 description = "Preparing upload directory structure",
686 command = ["mkdir", "-p", WithProperties("tmp/upload/%%(prefix)stargets/%s/%s" %(ts[0], ts[1]), prefix=GetVersionPrefix)],
690 factory.addStep(ShellCommand(
691 name = "linkprepare",
692 description = "Preparing repository symlink",
693 command = ["ln", "-s", "-f", WithProperties("../packages-%(basever)s", basever=GetBaseVersion), WithProperties("tmp/upload/%(prefix)spackages", prefix=GetVersionPrefix)],
694 doStepIf = IsNoMasterBuild,
698 factory.addStep(ShellCommand(
700 description = "Uploading directory structure",
701 command = ["rsync", "-4", "-avz", "tmp/upload/", "%s/" %(rsync_bin_url)],
702 env={'RSYNC_PASSWORD': rsync_bin_key},
703 haltOnFailure = True,
707 factory.addStep(ShellCommand(
708 name = "targetupload",
709 description = "Uploading target files",
710 command=["rsync", "-4", "--progress", "--delete", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]),
711 "-avz", WithProperties("bin/targets/%s/%s%%(libc)s/" %(ts[0], ts[1])),
712 WithProperties("%s/%%(prefix)stargets/%s/%s/" %(rsync_bin_url, ts[0], ts[1]), prefix=GetVersionPrefix)],
713 env={'RSYNC_PASSWORD': rsync_bin_key},
714 haltOnFailure = True,
718 if rsync_src_url is not None:
719 factory.addStep(ShellCommand(
720 name = "sourceupload",
721 description = "Uploading source archives",
722 command=["rsync", "-4", "--progress", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "dl/", "%s/" %(rsync_src_url)],
723 env={'RSYNC_PASSWORD': rsync_src_key},
724 haltOnFailure = True,
729 factory.addStep(ShellCommand(
730 name = "packageupload",
731 description = "Uploading package files",
732 command=["rsync", "-4", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "bin/packages/", "%s/packages/" %(rsync_bin_url)],
733 env={'RSYNC_PASSWORD': rsync_bin_key},
734 haltOnFailure = False,
740 factory.addStep(ShellCommand(
742 description = "Uploading logs",
743 command=["rsync", "-4", "--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])],
744 env={'RSYNC_PASSWORD': rsync_bin_key},
745 haltOnFailure = False,
750 from buildbot.config import BuilderConfig
752 c['builders'].append(BuilderConfig(name=target, slavenames=slaveNames, factory=factory, nextBuild=GetNextBuild))
755 ####### STATUS TARGETS
757 # 'status' is a list of Status Targets. The results of each build will be
758 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
759 # including web pages, email senders, and IRC bots.
763 from buildbot.status import html
764 from buildbot.status.web import authz, auth
766 if ini.has_option("status", "bind"):
767 if ini.has_option("status", "user") and ini.has_option("status", "password"):
768 authz_cfg=authz.Authz(
769 # change any of these to True to enable; see the manual for more
771 auth=auth.BasicAuth([(ini.get("status", "user"), ini.get("status", "password"))]),
772 gracefulShutdown = 'auth',
773 forceBuild = 'auth', # use this to test your slave once it is set up
774 forceAllBuilds = 'auth',
777 stopAllBuilds = 'auth',
778 cancelPendingBuild = 'auth',
780 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind"), authz=authz_cfg))
782 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind")))
785 from buildbot.status import words
787 if ini.has_option("irc", "host") and ini.has_option("irc", "nickname") and ini.has_option("irc", "channel"):
788 irc_host = ini.get("irc", "host")
790 irc_chan = ini.get("irc", "channel")
791 irc_nick = ini.get("irc", "nickname")
794 if ini.has_option("irc", "port"):
795 irc_port = ini.getint("irc", "port")
797 if ini.has_option("irc", "password"):
798 irc_pass = ini.get("irc", "password")
800 irc = words.IRC(irc_host, irc_nick, port = irc_port, password = irc_pass,
801 channels = [{ "channel": irc_chan }],
804 'successToFailure': 1,
805 'failureToSuccess': 1
809 c['status'].append(irc)
812 ####### PROJECT IDENTITY
814 # the 'title' string will appear at the top of this buildbot
815 # installation's html.WebStatus home page (linked to the
816 # 'titleURL') and is embedded in the title of the waterfall HTML page.
818 c['title'] = ini.get("general", "title")
819 c['titleURL'] = ini.get("general", "title_url")
821 # the 'buildbotURL' string should point to the location where the buildbot's
822 # internal web server (usually the html.WebStatus page) is visible. This
823 # typically uses the port number set in the Waterfall 'status' entry, but
824 # with an externally-visible host name which the buildbot cannot figure out
827 c['buildbotURL'] = ini.get("general", "buildbot_url")
832 # This specifies what database buildbot uses to store its state. You can leave
833 # this at its default for all but the largest installations.
834 'db_url' : "sqlite:///state.sqlite",