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
56 home_dir = os.path.abspath(ini.get("general", "homedir"))
64 if ini.has_option("general", "expire"):
65 tree_expire = ini.getint("general", "expire")
67 if ini.has_option("general", "other_builds"):
68 other_builds = ini.getint("general", "other_builds")
70 if ini.has_option("general", "cc_version"):
71 cc_version = ini.get("general", "cc_version").split()
72 if len(cc_version) == 1:
73 cc_version = ["eq", cc_version[0]]
75 repo_url = ini.get("repo", "url")
76 repo_branch = "master"
78 if ini.has_option("repo", "branch"):
79 repo_branch = ini.get("repo", "branch")
81 rsync_bin_url = ini.get("rsync", "binary_url")
82 rsync_bin_key = ini.get("rsync", "binary_password")
87 if ini.has_option("rsync", "source_url"):
88 rsync_src_url = ini.get("rsync", "source_url")
89 rsync_src_key = ini.get("rsync", "source_password")
93 gpg_comment = "Unattended build signature"
94 gpg_passfile = "/dev/null"
96 if ini.has_option("gpg", "home"):
97 gpg_home = ini.get("gpg", "home")
99 if ini.has_option("gpg", "keyid"):
100 gpg_keyid = ini.get("gpg", "keyid")
102 if ini.has_option("gpg", "comment"):
103 gpg_comment = ini.get("gpg", "comment")
105 if ini.has_option("gpg", "passfile"):
106 gpg_passfile = ini.get("gpg", "passfile")
112 if not os.path.isdir(home_dir+'/source.git'):
113 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, home_dir+'/source.git'])
115 subprocess.call(["git", "pull"], cwd = home_dir+'/source.git')
117 findtargets = subprocess.Popen([home_dir+'/dumpinfo.pl', 'targets'],
118 stdout = subprocess.PIPE, cwd = home_dir+'/source.git')
121 line = findtargets.stdout.readline()
124 ta = line.strip().split(' ')
125 targets.append(ta[0])
128 # the 'change_source' setting tells the buildmaster how it should find out
129 # about source code changes. Here we point to the buildbot clone of pyflakes.
131 from buildbot.changes.gitpoller import GitPoller
132 c['change_source'] = []
133 c['change_source'].append(GitPoller(
135 workdir=home_dir+'/work.git', branch=repo_branch,
140 # Configure the Schedulers, which decide how to react to incoming changes. In this
141 # case, just kick off a 'basebuild' build
143 from buildbot.schedulers.basic import SingleBranchScheduler
144 from buildbot.schedulers.forcesched import ForceScheduler
145 from buildbot.changes import filter
147 c['schedulers'].append(SingleBranchScheduler(
149 change_filter=filter.ChangeFilter(branch=repo_branch),
151 builderNames=targets))
153 c['schedulers'].append(ForceScheduler(
155 builderNames=targets))
159 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
160 # what steps, and which slaves can execute them. Note that any particular build will
161 # only take place on one slave.
163 from buildbot.process.factory import BuildFactory
164 from buildbot.steps.source.git import Git
165 from buildbot.steps.shell import ShellCommand
166 from buildbot.steps.shell import SetProperty
167 from buildbot.steps.transfer import FileUpload
168 from buildbot.steps.transfer import FileDownload
169 from buildbot.steps.master import MasterShellCommand
170 from buildbot.process.properties import WithProperties
174 [ "tools", "tools/clean" ],
175 [ "chain", "toolchain/clean" ],
176 [ "linux", "target/linux/clean" ],
177 [ "dir", "dirclean" ],
178 [ "dist", "distclean" ]
181 def IsCleanRequested(pattern):
182 def CheckCleanProperty(step):
183 val = step.getProperty("clean")
184 if val and re.match(pattern, val):
189 return CheckCleanProperty
191 def IsTaggingRequested(step):
192 val = step.getProperty("tag")
193 if val and re.match("^[0-9]+\.[0-9]+\.[0-9]+$", val):
198 def IsNoTaggingRequested(step):
199 return not IsTaggingRequested(step)
201 def IsNoMasterBuild(step):
202 return repo_branch != "master"
204 def GetBaseVersion(props):
205 if re.match("^[^-]+-[0-9]+\.[0-9]+$", repo_branch):
206 return repo_branch.split('-')[1]
210 def GetVersionPrefix(props):
211 basever = GetBaseVersion(props)
212 if props.hasProperty("tag") and re.match("^[0-9]+\.[0-9]+\.[0-9]+$", props["tag"]):
213 return "%s/" % props["tag"]
214 elif basever != "master":
215 return "%s-SNAPSHOT/" % basever
219 def GetNumJobs(props):
220 if props.hasProperty("slavename") and props.hasProperty("nproc"):
221 return ((int(props["nproc"]) / (max_builds[props["slavename"]] + other_builds)) + 1)
226 if props.hasProperty("cc_command"):
227 return props["cc_command"]
232 if props.hasProperty("cxx_command"):
233 return props["cxx_command"]
237 def MakeEnv(overrides=None):
239 'CC': WithProperties("%(cc)s", cc=GetCC),
240 'CXX': WithProperties("%(cxx)s", cxx=GetCXX)
242 if overrides is not None:
243 env.update(overrides)
249 dlLock = locks.SlaveLock("slave_dl")
250 tagLock = locks.MasterLock("make_tag")
252 checkBuiltin = re.sub('[\t\n ]+', ' ', """
254 local symbol op path file;
255 for file in $CHANGED_FILES; do
261 while read symbol op path; do
262 case "$symbol" in package-*)
263 symbol="${symbol##*(}";
264 symbol="${symbol%)}";
265 for file in $CHANGED_FILES; do
266 case "$file" in "package/$path/"*)
267 grep -qsx "$symbol=y" .config && return 0
271 done < tmp/.packagedeps;
277 class IfBuiltinShellCommand(ShellCommand):
278 def _quote(self, str):
279 if re.search("[^a-zA-Z0-9/_.-]", str):
280 return "'%s'" %(re.sub("'", "'\"'\"'", str))
283 def setCommand(self, command):
284 if not isinstance(command, (str, unicode)):
285 command = ' '.join(map(self._quote, command))
288 '%s; if checkBuiltin; then %s; else exit 0; fi' %(checkBuiltin, command)
291 def setupEnvironment(self, cmd):
292 slaveEnv = self.slaveEnvironment
296 for request in self.build.requests:
297 for source in request.sources:
298 for change in source.changes:
299 for file in change.files:
300 changedFiles[file] = True
301 fullSlaveEnv = slaveEnv.copy()
302 fullSlaveEnv['CHANGED_FILES'] = ' '.join(changedFiles.keys())
303 cmd.args['env'] = fullSlaveEnv
307 for slave in c['slaves']:
308 slaveNames.append(slave.slavename)
310 for target in targets:
311 ts = target.split('/')
313 factory = BuildFactory()
315 # find number of cores
316 factory.addStep(SetProperty(
319 description = "Finding number of CPUs",
320 command = ["nproc"]))
322 # find gcc and g++ compilers
323 if cc_version is not None:
324 factory.addStep(FileDownload(
325 mastersrc = "findbin.pl",
326 slavedest = "../findbin.pl",
329 factory.addStep(SetProperty(
331 property = "cc_command",
332 description = "Finding gcc command",
333 command = ["../findbin.pl", "gcc", cc_version[0], cc_version[1]],
334 haltOnFailure = True))
336 factory.addStep(SetProperty(
338 property = "cxx_command",
339 description = "Finding g++ command",
340 command = ["../findbin.pl", "g++", cc_version[0], cc_version[1]],
341 haltOnFailure = True))
343 # expire tree if needed
345 factory.addStep(FileDownload(
346 mastersrc = "expire.sh",
347 slavedest = "../expire.sh",
350 factory.addStep(ShellCommand(
352 description = "Checking for build tree expiry",
353 command = ["./expire.sh", str(tree_expire)],
355 haltOnFailure = True,
358 # user-requested clean targets
359 for tuple in CleanTargetMap:
360 factory.addStep(ShellCommand(
362 description = 'User-requested "make %s"' % tuple[1],
363 command = ["make", tuple[1], "V=s"],
365 doStepIf = IsCleanRequested(tuple[0])
368 factory.addStep(MasterShellCommand(
370 description = "Tagging Git repository",
371 command = [home_dir+'/maketag.sh', '-i', '-k', str(gpg_keyid or ''),
372 '-p', str(gpg_passfile or ''), '-v', WithProperties("%(tag:-)s")],
373 path = home_dir+'/source.git',
374 env = {'GNUPGHOME': gpg_home},
375 haltOnFailure = True,
376 doStepIf = IsTaggingRequested,
377 locks = [tagLock.access('exclusive')]
381 factory.addStep(ShellCommand(
382 name = "switchbranch",
383 description = "Checking out Git branch",
384 command = "if [ -d .git ]; then git checkout '%s'; else exit 0; fi" % repo_branch,
385 haltOnFailure = True,
386 doStepIf = IsNoTaggingRequested
389 # check out the source
392 branch = repo_branch,
393 mode = 'incremental',
397 factory.addStep(ShellCommand(
399 description = "Fetching Git remote refs",
400 command = ["git", "fetch", "origin", "+refs/heads/%s:refs/remotes/origin/%s" %(repo_branch, repo_branch)],
405 factory.addStep(ShellCommand(
407 description = "Fetching Git tags",
408 command = ["git", "fetch", "--tags", "--", repo_url],
409 haltOnFailure = True,
410 doStepIf = IsTaggingRequested
414 factory.addStep(ShellCommand(
416 description = "Checking out Git tag",
417 command = ["git", "checkout", WithProperties("tags/v%(tag:-)s")],
418 haltOnFailure = True,
419 doStepIf = IsTaggingRequested
422 factory.addStep(ShellCommand(
424 description = "Remove tmp folder",
425 command=["rm", "-rf", "tmp/"]))
428 # factory.addStep(ShellCommand(
429 # name = "feedsconf",
430 # description = "Copy the feeds.conf",
431 # command='''cp ~/feeds.conf ./feeds.conf''' ))
434 factory.addStep(ShellCommand(
435 name = "rmfeedlinks",
436 description = "Remove feed symlinks",
437 command=["rm", "-rf", "package/feeds/"]))
440 factory.addStep(ShellCommand(
441 name = "updatefeeds",
442 description = "Updating feeds",
443 command=["./scripts/feeds", "update"],
447 factory.addStep(ShellCommand(
448 name = "installfeeds",
449 description = "Installing feeds",
450 command=["./scripts/feeds", "install", "-a"],
454 factory.addStep(FileDownload(
455 mastersrc = "config.seed",
456 slavedest = ".config",
461 factory.addStep(ShellCommand(
463 description = "Seeding .config",
464 command = "printf 'CONFIG_TARGET_%s=y\\nCONFIG_TARGET_%s_%s=y\\n' >> .config" %(ts[0], ts[0], ts[1])
467 factory.addStep(ShellCommand(
469 description = "Removing output directory",
470 command = ["rm", "-rf", "bin/"]
473 factory.addStep(ShellCommand(
475 description = "Populating .config",
476 command = ["make", "defconfig"],
481 factory.addStep(ShellCommand(
483 description = "Checking architecture",
484 command = ["grep", "-sq", "CONFIG_TARGET_%s=y" %(ts[0]), ".config"],
492 factory.addStep(SetProperty(
495 description = "Finding libc suffix",
496 command = ["sed", "-ne", '/^CONFIG_LIBC=/ { s!^CONFIG_LIBC="\\(.*\\)"!\\1!; s!^musl$!!; s!.\\+!-&!p }', ".config"]))
499 factory.addStep(FileDownload(mastersrc=home_dir+'/key-build', slavedest="key-build", mode=0600))
500 factory.addStep(FileDownload(mastersrc=home_dir+'/key-build.pub', slavedest="key-build.pub", mode=0600))
503 factory.addStep(ShellCommand(
505 description = "Preparing dl/",
506 command = "mkdir -p $HOME/dl && rm -rf ./dl && ln -sf $HOME/dl ./dl",
512 factory.addStep(ShellCommand(
514 description = "Building GNU tar",
515 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "tools/tar/install", "V=s"],
521 factory.addStep(ShellCommand(
523 description = "Populating dl/",
524 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "download", "V=s"],
527 locks = [dlLock.access('exclusive')]
530 factory.addStep(ShellCommand(
532 description = "Cleaning base-files",
533 command=["make", "package/base-files/clean", "V=s"]
537 factory.addStep(ShellCommand(
539 description = "Building tools",
540 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "tools/install", "V=s"],
545 factory.addStep(ShellCommand(
547 description = "Building toolchain",
548 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "toolchain/install", "V=s"],
553 factory.addStep(ShellCommand(
555 description = "Building kmods",
556 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "target/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
558 #env={'BUILD_LOG_DIR': 'bin/%s' %(ts[0])},
562 factory.addStep(ShellCommand(
564 description = "Building packages",
565 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/compile", "V=s", "IGNORE_ERRORS=n m", "BUILD_LOG=1"],
567 #env={'BUILD_LOG_DIR': 'bin/%s' %(ts[0])},
571 # factory.addStep(IfBuiltinShellCommand(
572 factory.addStep(ShellCommand(
574 description = "Installing packages",
575 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/install", "V=s"],
580 factory.addStep(ShellCommand(
582 description = "Indexing packages",
583 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "package/index", "V=s"],
588 #factory.addStep(IfBuiltinShellCommand(
589 factory.addStep(ShellCommand(
591 description = "Building images",
592 command=["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "target/install", "V=s"],
597 factory.addStep(ShellCommand(
599 description = "Generating config.seed",
600 command=["make", "-j1", "diffconfig", "V=s"],
605 factory.addStep(ShellCommand(
607 description = "Calculating checksums",
608 command=["make", "-j1", "checksum", "V=s"],
614 if gpg_keyid is not None:
615 factory.addStep(MasterShellCommand(
616 name = "signprepare",
617 description = "Preparing temporary signing directory",
618 command = ["mkdir", "-p", "%s/signing" %(home_dir)],
622 factory.addStep(ShellCommand(
624 description = "Packing files to sign",
625 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])),
629 factory.addStep(FileUpload(
630 slavesrc = "sign.tar.gz",
631 masterdest = "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]),
635 factory.addStep(MasterShellCommand(
637 description = "Signing files",
638 command = ["%s/signall.sh" %(home_dir), "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]), gpg_keyid, gpg_comment],
639 env = {'GNUPGHOME': gpg_home, 'PASSFILE': gpg_passfile},
643 factory.addStep(FileDownload(
644 mastersrc = "%s/signing/%s.%s.tar.gz" %(home_dir, ts[0], ts[1]),
645 slavedest = "sign.tar.gz",
649 factory.addStep(ShellCommand(
651 description = "Unpacking signed files",
652 command = ["tar", "-xzf", "sign.tar.gz"],
657 factory.addStep(ShellCommand(
659 description = "Preparing upload directory structure",
660 command = ["mkdir", "-p", WithProperties("tmp/upload/%%(prefix)stargets/%s/%s" %(ts[0], ts[1]), prefix=GetVersionPrefix)],
664 factory.addStep(ShellCommand(
665 name = "linkprepare",
666 description = "Preparing repository symlink",
667 command = ["ln", "-s", "-f", WithProperties("../packages-%(basever)s", basever=GetBaseVersion), WithProperties("tmp/upload/%(prefix)spackages", prefix=GetVersionPrefix)],
668 doStepIf = IsNoMasterBuild,
672 factory.addStep(ShellCommand(
674 description = "Uploading directory structure",
675 command = ["rsync", "-avz", "tmp/upload/", "%s/" %(rsync_bin_url)],
676 env={'RSYNC_PASSWORD': rsync_bin_key},
677 haltOnFailure = True,
681 factory.addStep(ShellCommand(
682 name = "targetupload",
683 description = "Uploading target files",
684 command=["rsync", "--progress", "--delete", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]),
685 "-avz", WithProperties("bin/targets/%s/%s%%(libc)s/" %(ts[0], ts[1])),
686 WithProperties("%s/%%(prefix)stargets/%s/%s/" %(rsync_bin_url, ts[0], ts[1]), prefix=GetVersionPrefix)],
687 env={'RSYNC_PASSWORD': rsync_bin_key},
688 haltOnFailure = True,
692 if rsync_src_url is not None:
693 factory.addStep(ShellCommand(
694 name = "sourceupload",
695 description = "Uploading source archives",
696 command=["rsync", "--progress", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "dl/", "%s/" %(rsync_src_url)],
697 env={'RSYNC_PASSWORD': rsync_src_key},
698 haltOnFailure = True,
703 factory.addStep(ShellCommand(
704 name = "packageupload",
705 description = "Uploading package files",
706 command=["rsync", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s~%s" %(ts[0], ts[1]), "-avz", "bin/packages/", "%s/packages/" %(rsync_bin_url)],
707 env={'RSYNC_PASSWORD': rsync_bin_key},
708 haltOnFailure = False,
714 factory.addStep(ShellCommand(
716 description = "Uploading logs",
717 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])],
718 env={'RSYNC_PASSWORD': rsync_bin_key},
719 haltOnFailure = False,
724 from buildbot.config import BuilderConfig
726 c['builders'].append(BuilderConfig(name=target, slavenames=slaveNames, factory=factory))
729 ####### STATUS TARGETS
731 # 'status' is a list of Status Targets. The results of each build will be
732 # pushed to these targets. buildbot/status/*.py has a variety to choose from,
733 # including web pages, email senders, and IRC bots.
737 from buildbot.status import html
738 from buildbot.status.web import authz, auth
740 if ini.has_option("status", "bind"):
741 if ini.has_option("status", "user") and ini.has_option("status", "password"):
742 authz_cfg=authz.Authz(
743 # change any of these to True to enable; see the manual for more
745 auth=auth.BasicAuth([(ini.get("status", "user"), ini.get("status", "password"))]),
746 gracefulShutdown = 'auth',
747 forceBuild = 'auth', # use this to test your slave once it is set up
748 forceAllBuilds = 'auth',
751 stopAllBuilds = 'auth',
752 cancelPendingBuild = 'auth',
754 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind"), authz=authz_cfg))
756 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind")))
759 from buildbot.status import words
761 if ini.has_option("irc", "host") and ini.has_option("irc", "nickname") and ini.has_option("irc", "channel"):
762 irc_host = ini.get("irc", "host")
764 irc_chan = ini.get("irc", "channel")
765 irc_nick = ini.get("irc", "nickname")
768 if ini.has_option("irc", "port"):
769 irc_port = ini.getint("irc", "port")
771 if ini.has_option("irc", "password"):
772 irc_pass = ini.get("irc", "password")
774 irc = words.IRC(irc_host, irc_nick, port = irc_port, password = irc_pass,
775 channels = [{ "channel": irc_chan }],
778 'successToFailure': 1,
779 'failureToSuccess': 1
783 c['status'].append(irc)
786 ####### PROJECT IDENTITY
788 # the 'title' string will appear at the top of this buildbot
789 # installation's html.WebStatus home page (linked to the
790 # 'titleURL') and is embedded in the title of the waterfall HTML page.
792 c['title'] = ini.get("general", "title")
793 c['titleURL'] = ini.get("general", "title_url")
795 # the 'buildbotURL' string should point to the location where the buildbot's
796 # internal web server (usually the html.WebStatus page) is visible. This
797 # typically uses the port number set in the Waterfall 'status' entry, but
798 # with an externally-visible host name which the buildbot cannot figure out
801 c['buildbotURL'] = ini.get("general", "buildbot_url")
806 # This specifies what database buildbot uses to store its state. You can leave
807 # this at its default for all but the largest installations.
808 'db_url' : "sqlite:///state.sqlite",