3 Michael Stapelberg <michael@i3wm.org>
6 This document explains the http://www.buildbot.net/[buildbot] setup we use to
7 provide up-to-date documentation and debian packages at http://build.i3wm.org/.
8 We publish these information so that our setup is well-documented (thus
9 decreasing future maintenance effort) and because it might be interesting for
14 What we are doing in i3 is called Continuous Integration (see
15 http://en.wikipedia.org/wiki/Continuous_integration): we publish the changes we
16 make on our local machines as often as possible. In order to maintain a
17 continuously high quality, each time any developer pushes changes to the
18 official git repository, a number of quality assurance tools start running
21 1. Latest documentation is generated and provided at
22 http://build.i3wm.org/docs/. This makes it easy to link to documentation for
23 features which are only in the current git version, not in the released
25 2. The source code is compiled and it is automatically posted to the IRC
26 channel whether there were any compiler warnings. While developers should
27 notice compiler warnings, this mechanism creates a bit of public pressure
28 ("Oh, Michael introduced warnings with this commit!"). More importantly,
29 since this mechanism builds a dist tarball and then compiles that tarball,
30 any changes to the source which would result in an uncompilable dist tarball
31 are immediately obvious. Therefore, we could cut a release from the current
32 git version at any point in time.
33 3. The clang static analyzer runs and the resulting report is provided at
34 http://build.i3wm.org/clang-analyze/. While every developer needs to compile
35 his code before committing, he doesn’t necessarily use clang (so we catch
36 build failures when using clang) and he also probably doesn’t run a static
37 analyzer as part of his normal workflow. By just being available without any
38 friction, this mechanism encourages developers to look at the report and fix
40 4. Debian (and Ubuntu) packages are built. This not only ensures that we don’t
41 change anything in the source code which would lead to an FTBFS (Fails To
42 Build From Source) when building a Debian package, it also goes a long way
43 to encourage end users to test i3. To remove the need and resource
44 requirements for them to compile their own version of i3 regularly, we
45 provide packages that integrate conveniently with a normal Debian system
46 (e.g. that are automatically upgraded).
50 Previously, I was unsatisfied with the current state of FOSS CI tools like
51 Jenkins, Tinderbox and others. They either seemed bloated, hard to use,
52 outdated or unappealing for some other reason.
54 Then I discovered buildbot and was impressed by its flexibility. It let me
55 implement everything I wanted from a CI tool and (in my opinion) it is
56 light-weight, easy to deploy and well maintained.
58 The only downside of buildbot is its configuration and documentation: You need
59 to spend quite a bit of time (I needed multiple days) until it works the way
60 you want it to and oftentimes, the documentation is far too sparse. This is one
61 of the reasons why I’m publishing the i3 setup.
65 See the next section for a complete, copy & pasteable configuration file. This
66 section covers the most important aspects without covering every line.
68 This document assumes you are running buildbot 0.8.6p1.
72 Since i3 uses a central git repository, we use the official buildbot
73 https://github.com/buildbot/buildbot/blob/master/master/contrib/git_buildbot.py[git
74 post-receive hook] that sends the change information to the buildbot master.
78 There are two things (called "builders" in buildbot-language) which happen
79 whenever a new change in the +next+ branch of i3 occurs:
81 1. The "docs" builder builds and uploads the latest documentation. This happens
82 directly from the git repository with a custom asciidoc configuration which
83 indicates that these docs refer to the git version. Therefore, this builder
84 does not benefit from having a dist tarball available (contrary to the other
87 2. The "dist" builder prepares a dist tarball and then triggers the remaining
88 builders. This ensures that building the dist tarball (an operation which
89 takes about one minute due to documentation generation) only happens once.
91 Here is the relevant configuration part:
94 ---------------------------------------------
97 c['schedulers'].append(SingleBranchScheduler(
100 treeStableTimer = 10,
101 builderNames = [ 'dist', 'docs' ],
104 c['schedulers'].append(Triggerable(
105 name = 'dist-tarball-done',
106 builderNames = [ 'compile', 'clang-analyze', 'debian-packages', 'ubuntu-packages' ],
108 ---------------------------------------------
110 === Building the dist tarball
112 This builder clones the i3 git repository and runs "make dist", which creates a
113 tarball that could be named "i3-4.2.tar.bz2" for example. This tarball is then
114 renamed to dist-%(gitversion).tar.bz2 (so that we can work with a predictable
115 name in the next steps) and uploaded to the buildbot master (since we can have
116 multiple buildslaves, we cannot just let it rest on the buildslave that built
117 it). Afterwards, old dist tarballs are cleaned up and the remaining builders
120 *Building a dist tarball*:
121 ---------------------------------------------
124 f = factories['dist'] = BuildFactory()
126 # Check out the git repository.
129 # Fill the 'gitversion' property with the output of git describe --tags.
130 f.addStep(shell.SetProperty(command = 'git describe --tags', property = 'gitversion'))
132 # Build the dist tarball.
133 cmd(f, name = 'make dist', command = [ 'make', 'dist' ])
135 # Rename the created tarball to a well-known name.
137 name = 'rename tarball',
138 command = WithProperties('mv *.tar.bz2 dist-%(gitversion)s.tar.bz2'),
141 # Upload the dist tarball to the master (other factories download it later).
142 f.addStep(transfer.FileUpload(
143 slavesrc = WithProperties('dist-%(gitversion)s.tar.bz2'),
144 masterdest = WithProperties('distballs/dist-%(gitversion)s.tar.bz2'),
147 # Cleanup old dist tarballs (everything older than tree days).
148 f.addStep(master.MasterShellCommand(
149 command = "find distballs -mtime +3 -exec rm '{}' \;",
150 name = 'cleanup old dist tarballs',
153 # Everything worked fine, now trigger compilation.
155 schedulerNames = [ 'dist-tarball-done' ],
156 copy_properties = [ 'gitversion' ],
158 ---------------------------------------------
160 Three things are noteworthy about this part of the configuration:
162 1. For convenience, we call each factory +f+ (just like the global buildbot
163 config uses +c+ for the top-level configuration) and add it to a dictionary.
164 Factories in that dictionary are later automatically configured for each
167 2. We have a shared step called +s_git+ so that we only have one location in
168 the configuration file where we specify the git repository URL and branch.
170 3. We have a custom function called +cmd+ which is a shortcut for defining a
171 +ShellCommand+ with +haltOnFailure=True+ (since each step is critical) and
172 +logEnviron=False+ (for brevity).
174 Here are their definitions:
177 ---------------------------------------------
178 def cmd(factory, **kwargs):
179 factory.addStep(ShellCommand(
180 haltOnFailure = True,
184 ---------------------------------------------
187 ---------------------------------------------
189 repourl = 'git://code.i3wm.org/i3',
192 # Check out the latest revision, not the one which caused this build.
193 alwaysUseLatest = True,
195 # We cannot use shallow because it breaks git describe --tags.
198 # Delete remnants of previous builds.
201 # Store checkouts in source/ and copy them over to build/ to save
205 ---------------------------------------------
207 === Compiling the dist tarball
209 For this builder to work, you obviously need to install all the
210 build-dependencies for your software on each buildslave. In the case of i3,
211 this can be done with +apt-get build-dep i3-wm+.
213 The compilation is pretty straight-forward since it uses the builtin +Compile+
214 step. We call +make+ with +-j4+ (we don’t have enough buildslaves to make
215 figuring out the amount of cores at build-time worthwhile) and +DEBUG=0+ to
216 simulate release build conditions. Also, we pass the preprocessor flag
217 +-D_FORTIFY_SOURCE=2+ and the compiler flags +-Wformat+ and +-Wformat-security+
218 to enable additional warnings.
220 *Compiling the dist tarball*:
221 ---------------------------------------------
222 f = factories['compile'] = BuildFactory()
223 unpack_dist_tarball(f)
225 command = [ 'make', 'DEBUG=0', '-j4' ],
226 warningPattern = '.*warning: ',
227 warnOnWarnings = True,
228 workdir = 'build/DIST',
230 'CPPFLAGS': '-D_FORTIFY_SOURCE=2',
231 'CFLAGS': '-Wformat -Wformat-security'
235 f.addStep(WarningsToIRC())
236 ---------------------------------------------
238 Again, we use custom functions (and a custom buildstep) to make our lives
239 easier. Here is the definition of unpack_dist_tarball which adds three steps to
240 the factory that download and unpack the dist tarball to the +DIST/+ directory:
242 *unpack_dist_tarball*:
243 ---------------------------------------------
244 def unpack_dist_tarball(factory):
245 factory.addStep(transfer.FileDownload(
246 mastersrc = WithProperties('distballs/dist-%(gitversion)s.tar.bz2'),
247 slavedest = 'dist.tar.bz2',
250 factory.addStep(slave.MakeDirectory(dir = 'build/DIST'))
253 name = 'unpack dist tarball',
254 command = [ 'tar', 'xf', 'dist.tar.bz2', '-C', 'DIST', '--strip-components=1' ],
256 ---------------------------------------------
258 The +WarningsToIRC+ build step is a custom build step which sets a property
259 called "ircsuffix" that is used by our custom IRC bot. This is covered later in
260 more detail. This property gets set to a green or red message, depending on
261 whether there were any warnings:
264 ---------------------------------------------
265 class WarningsToIRC(buildstep.BuildStep):
267 warnings = self.getProperty("warnings-count")
268 if warnings is not None and int(warnings) > 0:
269 warnings = int(warnings) # just to be sure
270 self.setProperty("ircsuffix", ("\0037 with %d warning%s!" %
271 (warnings, "s" if warnings != 1 else "")))
273 self.setProperty("ircsuffix", "\0033 without warnings")
274 self.finished(SUCCESS)
275 ---------------------------------------------
277 === Static code analysis
279 For this builder to work, you additionally need the +clang+ compiler on each
280 buildslave: +apt-get install clang+.
282 This builder uses only custom functions which you already know by now. It runs
283 scan-build, then moves scan-build’s output from a date-based directory directly
284 into the +CLANG/+ directory and uploads that to the buildmaster.
286 On the buildmaster, a webserver is configured which has a symlink to
287 +/home/build/i3-master/htdocs/clang-analyze+ in its document root.
289 *static code analysis*:
290 ---------------------------------------------
291 f = factories['clang-analyze'] = BuildFactory()
292 unpack_dist_tarball(f)
298 '--html-title', WithProperties('Analysis of i3 v%(gitversion)s'),
301 workdir = 'build/DIST',
304 # remove the subdirectory -- we always want to overwrite
305 cmd(f, command = 'mv CLANG/*/* CLANG/')
307 f.addStep(transfer.DirectoryUpload(
309 masterdest = 'htdocs/clang-analyze',
311 name = 'upload output',
314 f.addStep(ClangToIRC())
315 ---------------------------------------------
317 The +ClangToIRC+ custom step is even simpler than +WarningsToIRC+. It simply
318 sets the ircsuffix property to a static message:
321 ---------------------------------------------
322 class ClangToIRC(buildstep.BuildStep):
324 self.setProperty("ircsuffix", ", see http://build.i3wm.org/clang-analyze/")
325 self.finished(SUCCESS)
326 ---------------------------------------------
328 === Generating documentation
330 This builder is the one which is the least clean of all. It uses the Debian
331 packaging information to decide which docs to publish and which manpages to
332 generate. Additionally, it uses a for loop instead of calling a script. I
333 recommend including a script to do this in your repository instead.
335 Apart from these concerns, the builder is straight-forward: It clones the git
336 repository, generates the documentation and then uploads the documentation to
339 *Generating documentation*:
340 ---------------------------------------------
341 f = factories['docs'] = BuildFactory()
343 # Fill the 'gitversion' property with the output of git describe --tags.
344 f.addStep(shell.SetProperty(command = 'git describe --tags', property = 'gitversion'))
345 cmd(f, name = 'build docs', command = [ 'make', '-C', 'docs', "ASCIIDOC=asciidoc -a linkcss -a stylesdir=http://i3wm.org/css -a scriptsdir=http://i3wm.org/js --backend=xhtml11 -f docs/asciidoc-git.conf" ])
346 cmd(f, name = 'build manpages', command = "for file in $(sed 's/\.1$/.man/g' debian/i3-wm.manpages); do asciidoc -a linkcss -a stylesdir=http://i3wm.org/css -a scriptsdir=http://i3wm.org/js --backend=xhtml11 -f docs/asciidoc-git.conf \"$file\"; done")
347 f.addStep(slave.MakeDirectory(dir='build/COPY-DOCS'))
348 cmd(f, name = 'copy docs', command = "cp $(tr '\\n' ' ' < debian/i3-wm.docs) COPY-DOCS")
349 cmd(f, name = 'copy manpages', command = "cp $(sed 's/\.1$/.html/g' debian/i3-wm.manpages | tr '\\n' ' ') COPY-DOCS")
351 f.addStep(transfer.DirectoryUpload(
352 slavesrc = 'COPY-DOCS',
353 masterdest = 'htdocs/docs-git',
355 name = 'upload docs'))
357 f.addStep(DocsToIRC())
358 ---------------------------------------------
360 Just as +ClangToIRC+, +DocsToIRC+ appends a static message:
363 ---------------------------------------------
364 class DocsToIRC(buildstep.BuildStep):
366 self.setProperty("ircsuffix", ", see http://build.i3wm.org/docs/")
367 self.finished(SUCCESS)
368 ---------------------------------------------
370 === Building Debian/Ubuntu packages
372 This is the most complex builder of all. It uses +pbuilder-dist+, +debchange+,
373 +dpkg-buildpackage+ and +reprepro+ to generate a Debian repository with a
374 cleanly compiled package for amd64 and i386. In order for it to work, you need
375 to install the following packages: +apt-get install devscripts dpkg-dev
376 reprepro ubuntu-dev-tools+. Afterwards, you need to allow the user as which the
377 buildslave runs to execute pbuilder via sudo without needing a password, so run
378 +visudo+ and add a line like this one:
381 ---------------------------------------------
382 build ALL= NOPASSWD: SETENV: /usr/sbin/pbuilder
383 ---------------------------------------------
385 Then, as the user as which your buildslave runs, setup the pbuilder
386 environments (you only need to do this once):
388 *pbuilder preparation*:
389 ---------------------------------------------
390 sudo ln -s pbuilder-dist /usr/bin/pbuilder-sid-amd64
391 sudo ln -s pbuilder-dist /usr/bin/pbuilder-sid-i386
392 pbuilder-sid-amd64 create
393 pbuilder-sid-i386 create
394 ---------------------------------------------
396 Also, you will need a GPG key to sign these packages.
398 The debian builder starts by unpacking the dist tarball, copying the Debian
399 packaging from git, creating an empty Debian repository with the
400 +i3-autobuild-keyring+ contents in it. It then adds a new changelog entry to
401 reflect the git version and the fact that this package was built automatically,
402 builds a source package with +dpkg-buildpackage+ and adds it to the repository.
403 Afterwards, it updates each pbuilder and builds binary packages for each
404 architecture (amd64 and i386). After adding the resulting packages to the
405 repository, it uploads the repository to the buildmaster:
408 ---------------------------------------------
409 distributions = [ 'sid-amd64', 'sid-i386' ]
412 f = factories['debian-packages'] = BuildFactory()
413 # We need the git repository for the Debian packaging.
415 unpack_dist_tarball(f)
416 cmd(f, name = 'copy packaging', command = "cp -r debian DIST/")
418 # Add a new changelog entry to have the git version in the package version.
420 name = 'update changelog',
421 workdir = 'build/DIST',
422 command = [ 'debchange', '-m', '-l', WithProperties('+g%(gitversion)s'), 'Automatically built' ],
427 command = [ 'dpkg-buildpackage', '-S', '-us', '-uc' ],
428 workdir = 'build/DIST',
431 for dist in distributions:
432 f.addStep(slave.MakeDirectory(dir = 'build/RESULT-' + dist))
434 # Create debian sid repository
435 f.addStep(slave.MakeDirectory(dir = 'build/REPO-sid/conf'))
436 f.addStep(transfer.StringDownload(
439 Architectures: i386 amd64 source
441 DebIndices: Packages Release . .gz .bz2
442 DscIndices: Sources Release . .gz .bz2
443 SignWith: %(gpg_key)s
444 """ % { "gpg_key": gpg_key },
445 slavedest = 'REPO-sid/conf/distributions',
448 # add source package to repository
449 reprepro_include(f, 'i3-wm*_source.changes', 'dsc')
451 # Add keyring to the repository. We need to run git clone on our own because
452 # the Git() step assumes there’s precisely one repository we want to deal with.
453 # No big deal since the i3-autobuild-keyring repository is not big.
455 name = 'clone keyring repo',
456 command = 'git clone git://code.i3wm.org/i3-autobuild-keyring',
458 reprepro_include(f, 'i3-autobuild-keyring/prebuilt/*.changes')
460 for dist in distributions:
461 # update the pbuilder
462 cmd(f, name = 'update builder', command = 'pbuilder-' + dist + ' update')
464 # build the package for each dist
465 f.addStep(ShellCommand(
467 name = 'pkg ' + dist,
468 command = 'pbuilder-' + dist + ' build --binary-arch \
469 --buildresult RESULT-' + dist + ' --debbuildopts -j8 i3-wm*dsc',
473 reprepro_include(f, 'RESULT-' + dist + '/*.changes')
475 # upload the sid repo
476 # Since the next step is cleaning up old files, we set haltOnFailure=True -- we
477 # prefer providing old packages over providing no packages at all :).
478 for directory in [ 'pool', 'dists' ]:
479 f.addStep(transfer.DirectoryUpload(
480 slavesrc = 'REPO-sid/' + directory,
481 masterdest = 'htdocs/debian/sid/' + directory,
483 name = 'upload sid ' + directory,
484 haltOnFailure = True,
487 f.addStep(master.MasterShellCommand(
488 command = "find htdocs/debian/sid/pool -mtime +3 -exec rm '{}' \;",
489 name = 'cleanup old packages',
492 # We ensure there is an empty i18n/Index to speed up apt (so that it does not
493 # try to download Translation-*)
494 f.addStep(master.MasterShellCommand(
495 command = [ 'mkdir', '-p', 'htdocs/debian/sid/dists/sid/main/i18n' ],
496 name = 'create i18n folder',
498 f.addStep(master.MasterShellCommand(
499 command = [ 'touch', 'htdocs/debian/sid/dists/sid/main/i18n/Index' ],
500 name = 'touch i18n/Index',
502 ---------------------------------------------
504 The +reprepro_include+ command is defined as follows:
507 ---------------------------------------------
508 def reprepro_include(factory, path, debtype='deb', **kwargs):
510 name = 'reprepro include',
511 command = 'reprepro --ignore=wrongdistribution -T ' + debtype + ' -b REPO-sid include sid ' + path,
514 ---------------------------------------------
516 Running such a builder for Ubuntu works exactly the same way, but you need to
517 replace "sid" with "precise" in all places (see the full configuration file for
522 We don’t advertise the HTTP status target. Instead, status is posted to IRC via
523 a custom bot. This bot provides an HTTP end point and buildbot is configured to
524 push status changes to that endpoint:
526 *http status target*:
527 ---------------------------------------------
528 c['status'].append(buildbot.status.status_push.HttpStatusPush(
529 serverUrl = 'http://localhost:8080/push_buildbot',
531 ---------------------------------------------
533 You can find the source code of that bot at
534 http://code.stapelberg.de/git/go-buildbot-announce/. As the name suggests, it
535 is written in Go. Also, it is quite specific to i3, so you might be better off
536 implementing such a bot (or plugin) on your own. It might make for a nice
537 example, though, especially back when its only feature was announcing the build
540 http://code.stapelberg.de/git/go-buildbot-announce/tree/src/i3build.go?id=eeebf1a546454c8a0d82ca623886bb835cd32ba0
542 == Full configuration file
544 This is the full configuration file, as tested and currently in use (except for
545 the passwords, though):
548 ---------------------------------------------
551 # vim:ts=4:sw=4:expandtab:syntax=python
553 # i3 buildbot configuration
554 # © 2012 Michael Stapelberg, Public Domain
555 # see http://i3wm.org/docs/buildbot.html for more information.
557 from buildbot.buildslave import BuildSlave
558 from buildbot.changes import pb
559 from buildbot.schedulers.basic import SingleBranchScheduler
560 from buildbot.schedulers.triggerable import Triggerable
561 from buildbot.process.properties import WithProperties
562 from buildbot.process.factory import BuildFactory
563 from buildbot.steps.source.git import Git
564 from buildbot.steps.shell import ShellCommand
565 from buildbot.steps.shell import Compile
566 from buildbot.steps.trigger import Trigger
567 from buildbot.steps import shell, transfer, master, slave
568 from buildbot.config import BuilderConfig
569 from buildbot.process import buildstep
570 from buildbot.status import html
571 from buildbot.status import words
572 import buildbot.status.status_push
573 from buildbot.status.web import auth, authz
574 from buildbot.status.builder import SUCCESS, FAILURE
576 c = BuildmasterConfig = {}
578 c['slaves'] = [BuildSlave('docsteel-vm', 'secret')]
579 c['slavePortnum'] = 9989
580 # Changes are pushed to buildbot using a git hook.
581 c['change_source'] = [pb.PBChangeSource(
586 ################################################################################
588 ################################################################################
592 # The first scheduler kicks off multiple builders:
593 # • 'dist' builds a dist tarball and starts the triggerable schedulers
595 # • 'docs' builds the documentation with a special asciidoc configuration
596 # (therefore, it does not profit from a dist tarball and can be run in
598 c['schedulers'].append(SingleBranchScheduler(
601 treeStableTimer = 10,
602 builderNames = [ 'dist', 'docs' ],
605 c['schedulers'].append(Triggerable(
606 name = 'dist-tarball-done',
607 builderNames = [ 'compile', 'clang-analyze', 'debian-packages', 'ubuntu-packages' ],
610 ################################################################################
611 # Shortcuts for builders
612 ################################################################################
614 # shortcut for a ShellCommand with haltOnFailure=True, logEnviron=False
615 def cmd(factory, **kwargs):
616 factory.addStep(ShellCommand(
622 # Shortcut to add steps necessary to download and unpack the dist tarball.
623 def unpack_dist_tarball(factory):
624 factory.addStep(transfer.FileDownload(
625 mastersrc=WithProperties('distballs/dist-%(gitversion)s.tar.bz2'),
626 slavedest='dist.tar.bz2',
628 factory.addStep(slave.MakeDirectory(dir='build/DIST'))
630 name = 'unpack dist tarball',
631 command = [ 'tar', 'xf', 'dist.tar.bz2', '-C', 'DIST', '--strip-components=1' ],
634 # Includes the given path in REPO-sid using reprepro.
635 def reprepro_include(factory, path, debtype='deb', **kwargs):
637 name = 'reprepro include',
638 command = 'reprepro --ignore=wrongdistribution -T ' + debtype + ' -b REPO-sid include sid ' + path,
642 def reprepro_include_ubuntu(factory, path, debtype='deb', **kwargs):
644 name = 'reprepro include',
645 command = 'reprepro --ignore=wrongdistribution -T ' + debtype + ' -b REPO-sid include precise ' + path,
649 ################################################################################
651 ################################################################################
653 # Adds the ircsuffix property to reflect whether there were warnings.
654 class WarningsToIRC(buildstep.BuildStep):
656 warnings = self.getProperty("warnings-count")
657 if warnings is not None and int(warnings) > 0:
658 warnings = int(warnings) # just to be sure
659 self.setProperty("ircsuffix", "\0037 with %d warning%s!" % (warnings, "s" if warnings != 1 else ""))
661 self.setProperty("ircsuffix", "\0033 without warnings")
662 self.finished(SUCCESS)
664 # Adds a link to the automatically generated documentation.
665 class DocsToIRC(buildstep.BuildStep):
667 self.setProperty("ircsuffix", ", see http://build.i3wm.org/docs/")
668 self.finished(SUCCESS)
670 # Adds a link to the clang report.
671 class ClangToIRC(buildstep.BuildStep):
673 self.setProperty("ircsuffix", ", see http://build.i3wm.org/clang-analyze/")
674 self.finished(SUCCESS)
676 ################################################################################
677 # Shared steps, used in different factories.
678 ################################################################################
681 repourl='git://code.i3wm.org/i3',
684 # Check out the latest revision, not the one which caused this build.
685 alwaysUseLatest=True,
687 # We cannot use shallow because it breaks git describe --tags.
690 # Delete remnants of previous builds.
693 # Store checkouts in source/ and copy them over to build/ to save
697 # XXX: In newer versions of buildbot (> 0.8.6), we want to use
698 # getDescription={ 'tags': True } here and get rid of the extra git
699 # describe --tags step.
702 ################################################################################
703 # factory: "dist" — builds the dist tarball once (used by all other factories)
704 ################################################################################
708 f = factories['dist'] = BuildFactory()
709 # Check out the git repository.
711 # Fill the 'gitversion' property with the output of git describe --tags.
712 f.addStep(shell.SetProperty(command = 'git describe --tags', property = 'gitversion'))
713 # Build the dist tarball.
714 cmd(f, name = 'make dist', command = [ 'make', 'dist' ])
715 # Rename the created tarball to a well-known name.
716 cmd(f, name = 'rename tarball', command = WithProperties('mv *.tar.bz2 dist-%(gitversion)s.tar.bz2'))
717 # Upload the dist tarball to the master (other factories download it later).
718 f.addStep(transfer.FileUpload(
719 slavesrc = WithProperties('dist-%(gitversion)s.tar.bz2'),
720 masterdest = WithProperties('distballs/dist-%(gitversion)s.tar.bz2'),
722 # Cleanup old dist tarballs (everything older than tree days).
723 f.addStep(master.MasterShellCommand(
724 command = "find distballs -mtime +3 -exec rm '{}' \;",
725 name = 'cleanup old dist tarballs',
727 # Everything worked fine, now trigger compilation.
729 schedulerNames = [ 'dist-tarball-done' ],
730 copy_properties = [ 'gitversion' ],
733 ################################################################################
734 # factory: "compile" — compiles the dist tarball and reports warnings
735 ################################################################################
737 f = factories['compile'] = BuildFactory()
738 unpack_dist_tarball(f)
740 command = [ 'make', 'DEBUG=0', '-j4' ],
741 warningPattern = '.*warning: ',
742 warnOnWarnings = True,
743 workdir = 'build/DIST',
745 'CPPFLAGS': '-D_FORTIFY_SOURCE=2',
746 'CFLAGS': '-Wformat -Wformat-security'
750 f.addStep(WarningsToIRC())
752 ################################################################################
753 # factory: "clang-analyze" — runs a static code analysis
754 ################################################################################
755 # $ sudo apt-get install clang
757 f = factories['clang-analyze'] = BuildFactory()
758 unpack_dist_tarball(f)
764 '--html-title', WithProperties('Analysis of i3 v%(gitversion)s'),
767 workdir = 'build/DIST',
770 # remove the subdirectory -- we always want to overwrite
771 cmd(f, command = 'mv CLANG/*/* CLANG/')
773 f.addStep(transfer.DirectoryUpload(
775 masterdest = 'htdocs/clang-analyze',
777 name = 'upload output',
780 f.addStep(ClangToIRC())
782 ################################################################################
783 # factory: "docs" — builds documentation with a special asciidoc conf
784 ################################################################################
786 f = factories['docs'] = BuildFactory()
788 # Fill the 'gitversion' property with the output of git describe --tags.
789 f.addStep(shell.SetProperty(command = 'git describe --tags', property = 'gitversion'))
790 cmd(f, name = 'build docs', command = [ 'make', '-C', 'docs', "ASCIIDOC=asciidoc -a linkcss -a stylesdir=http://i3wm.org/css -a scriptsdir=http://i3wm.org/js --backend=xhtml11 -f docs/asciidoc-git.conf" ])
791 cmd(f, name = 'build manpages', command = "for file in $(sed 's/\.1$/.man/g' debian/i3-wm.manpages); do asciidoc -a linkcss -a stylesdir=http://i3wm.org/css -a scriptsdir=http://i3wm.org/js --backend=xhtml11 -f docs/asciidoc-git.conf \"$file\"; done")
792 f.addStep(slave.MakeDirectory(dir='build/COPY-DOCS'))
793 cmd(f, name = 'copy docs', command = "cp $(tr '\\n' ' ' < debian/i3-wm.docs) COPY-DOCS")
794 cmd(f, name = 'copy manpages', command = "cp $(sed 's/\.1$/.html/g' debian/i3-wm.manpages | tr '\\n' ' ') COPY-DOCS")
796 f.addStep(transfer.DirectoryUpload(
797 slavesrc = 'COPY-DOCS',
798 masterdest = 'htdocs/docs-git',
800 name = 'upload docs'))
802 f.addStep(DocsToIRC())
804 ################################################################################
805 # factory: "debian-packages" — builds Debian (sid) packages for amd64 and i386
806 ################################################################################
808 distributions = [ 'sid-amd64', 'sid-i386' ]
811 f = factories['debian-packages'] = BuildFactory()
812 # We need the git repository for the Debian packaging.
814 unpack_dist_tarball(f)
815 cmd(f, name='copy packaging', command = "cp -r debian DIST/")
817 # Add a new changelog entry to have the git version in the package version.
819 name = 'update changelog',
820 workdir = 'build/DIST',
821 command = [ 'debchange', '-m', '-l', WithProperties('+g%(gitversion)s'), 'Automatically built' ],
826 command = [ 'dpkg-buildpackage', '-S', '-us', '-uc' ],
827 workdir = 'build/DIST',
830 for dist in distributions:
831 f.addStep(slave.MakeDirectory(dir='build/RESULT-' + dist))
833 # Create debian sid repository
834 f.addStep(slave.MakeDirectory(dir='build/REPO-sid/conf'))
835 f.addStep(transfer.StringDownload(
838 Architectures: i386 amd64 source
840 DebIndices: Packages Release . .gz .bz2
841 DscIndices: Sources Release . .gz .bz2
842 SignWith: %(gpg_key)s
843 """ % { "gpg_key": gpg_key },
844 slavedest = 'REPO-sid/conf/distributions',
847 # add source package to repository
848 reprepro_include(f, 'i3-wm*_source.changes', 'dsc')
850 # Add keyring to the repository. We need to run git clone on our own because
851 # the Git() step assumes there’s precisely one repository we want to deal with.
852 # No big deal since the i3-autobuild-keyring repository is not big.
853 cmd(f, name='clone keyring repo', command = 'git clone git://code.i3wm.org/i3-autobuild-keyring')
854 reprepro_include(f, 'i3-autobuild-keyring/prebuilt/*.changes')
856 for dist in distributions:
857 # update the pbuilder
858 cmd(f, name = 'update builder', command = 'pbuilder-' + dist + ' update')
860 # build the package for each dist
861 f.addStep(ShellCommand(
863 name = 'pkg ' + dist,
864 command = 'pbuilder-' + dist + ' build --binary-arch \
865 --buildresult RESULT-' + dist + ' --debbuildopts -j8 i3-wm*dsc',
869 reprepro_include(f, 'RESULT-' + dist + '/*.changes')
871 # upload the sid repo
872 # Since the next step is cleaning up old files, we set haltOnFailure=True -- we
873 # prefer providing old packages over providing no packages at all :).
874 for directory in [ 'pool', 'dists' ]:
875 f.addStep(transfer.DirectoryUpload(
876 slavesrc = 'REPO-sid/' + directory,
877 masterdest = 'htdocs/debian/sid/' + directory,
879 name = 'upload sid ' + directory,
880 haltOnFailure = True,
883 f.addStep(master.MasterShellCommand(
884 command = "find htdocs/debian/sid/pool -mtime +3 -exec rm '{}' \;",
885 name = 'cleanup old packages',
888 # We ensure there is an empty i18n/Index to speed up apt (so that it does not
889 # try to download Translation-*)
890 f.addStep(master.MasterShellCommand(
891 command = [ 'mkdir', '-p', 'htdocs/debian/sid/dists/sid/main/i18n' ],
892 name = 'create i18n folder',
894 f.addStep(master.MasterShellCommand(
895 command = [ 'touch', 'htdocs/debian/sid/dists/sid/main/i18n/Index' ],
896 name = 'touch i18n/Index',
899 ################################################################################
900 # factory: "ubuntu-packages" — builds Ubuntu (precise) packages for amd64 and i386
901 ################################################################################
903 distributions = [ 'precise-amd64', 'precise-i386' ]
906 f = factories['ubuntu-packages'] = BuildFactory()
907 # We need the git repository for the Debian packaging.
909 unpack_dist_tarball(f)
910 cmd(f, name='copy packaging', command = "cp -r debian DIST/")
912 # Add a new changelog entry to have the git version in the package version.
914 name = 'update changelog',
915 workdir = 'build/DIST',
916 command = [ 'debchange', '-m', '-l', WithProperties('+g%(gitversion)s'), 'Automatically built' ],
921 command = [ 'dpkg-buildpackage', '-S', '-us', '-uc' ],
922 workdir = 'build/DIST',
925 for dist in distributions:
926 f.addStep(slave.MakeDirectory(dir='build/RESULT-' + dist))
928 # Create debian sid repository
929 f.addStep(slave.MakeDirectory(dir='build/REPO-sid/conf'))
930 f.addStep(transfer.StringDownload(
933 Architectures: i386 amd64 source
935 DebIndices: Packages Release . .gz .bz2
936 DscIndices: Sources Release . .gz .bz2
937 SignWith: %(gpg_key)s
938 """ % { "gpg_key": gpg_key },
939 slavedest = 'REPO-sid/conf/distributions',
942 # add source package to repository
943 reprepro_include_ubuntu(f, 'i3-wm*_source.changes', 'dsc')
945 # Add keyring to the repository. We need to run git clone on our own because
946 # the Git() step assumes there’s precisely one repository we want to deal with.
947 # No big deal since the i3-autobuild-keyring repository is not big.
948 cmd(f, name='clone keyring repo', command = 'git clone git://code.i3wm.org/i3-autobuild-keyring')
949 reprepro_include_ubuntu(f, 'i3-autobuild-keyring/prebuilt/*.changes')
951 for dist in distributions:
952 # update the pbuilder
953 cmd(f, name = 'update builder', command = 'pbuilder-' + dist + ' update')
955 # build the package for each dist
956 f.addStep(ShellCommand(
958 name = 'pkg ' + dist,
959 command = 'pbuilder-' + dist + ' build --binary-arch \
960 --buildresult RESULT-' + dist + ' --debbuildopts -j8 i3-wm*dsc',
964 reprepro_include_ubuntu(f, 'RESULT-' + dist + '/*.changes')
966 # upload the sid repo
967 # Since the next step is cleaning up old files, we set haltOnFailure=True -- we
968 # prefer providing old packages over providing no packages at all :).
969 for directory in [ 'pool', 'dists' ]:
970 f.addStep(transfer.DirectoryUpload(
971 slavesrc = 'REPO-sid/' + directory,
972 masterdest = 'htdocs/ubuntu/precise/' + directory,
974 name = 'upload precise ' + directory,
975 haltOnFailure = True,
978 f.addStep(master.MasterShellCommand(
979 command = "find htdocs/ubuntu/precise/pool -mtime +3 -exec rm '{}' \;",
980 name = 'cleanup old packages',
983 # We ensure there is an empty i18n/Index to speed up apt (so that it does not
984 # try to download Translation-*)
985 f.addStep(master.MasterShellCommand(
986 command = [ 'mkdir', '-p', 'htdocs/ubuntu/precise/dists/sid/main/i18n' ],
987 name = 'create i18n folder',
989 f.addStep(master.MasterShellCommand(
990 command = [ 'touch', 'htdocs/ubuntu/precise/dists/sid/main/i18n/Index' ],
991 name = 'touch i18n/Index',
997 # Add all builders to all buildslaves.
998 for factoryname in factories.keys():
999 c['builders'].append(BuilderConfig(
1001 slavenames=['docsteel-vm'],
1002 factory=factories[factoryname],
1006 ####### STATUS TARGETS
1010 authz_cfg=authz.Authz(
1011 gracefulShutdown = False,
1013 forceAllBuilds = False,
1014 pingBuilder = False,
1016 stopAllBuilds = False,
1017 cancelPendingBuild = False,
1020 c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg))
1022 c['status'].append(buildbot.status.status_push.HttpStatusPush(
1023 serverUrl = 'http://localhost:8080/push_buildbot',
1026 ####### PROJECT IDENTITY
1029 c['titleURL'] = 'http://i3wm.org/'
1030 # Removed so that search engines don’t crawl it
1031 c['buildbotURL'] = 'http://localhost/'
1036 # This specifies what database buildbot uses to store its state. You can leave
1037 # this at its default for all but the largest installations.
1038 'db_url' : "sqlite:///state.sqlite",
1040 ---------------------------------------------