Now that Debian has migrated away from alioth and towards a gitlab instance known as salsa, we get a pretty advanced Continuous Integration system for (almost) free. Having that, it might make sense to use that setup to autobuild and -test a package when committing something. I had a look at doing so for one of my packages, ola; the reason I chose that package is because it comes with an autopkgtest, so that makes testing it slightly easier (even if the autopkgtest is far from complete).
Gitlab CI is configured through a .gitlab-ci.yml
file, which supports
many options and may therefore be a bit complicated for first-time
users. Since I've worked with it before, I understand how it works, so
I thought it might be useful to show people how you can do things.
First, let's look at the .gitlab-ci.yml
file which I wrote for the ola
package:
stages:
- build
- autopkgtest
.build: &build
before_script:
- apt-get update
- apt-get -y install devscripts adduser fakeroot sudo
- mk-build-deps -t "apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends" -i -r
- adduser --disabled-password --gecos "" builduser
- chown -R builduser:builduser .
- chown builduser:builduser ..
stage: build
artifacts:
paths:
- built
script:
- sudo -u builduser dpkg-buildpackage -b -rfakeroot
after_script:
- mkdir built
- dcmd mv ../*ges built/
.test: &test
before_script:
- apt-get update
- apt-get -y install autopkgtest
stage: autopkgtest
script:
- autopkgtest built/*ges -- null
build:testing:
<<: *build
image: debian:testing
build:unstable:
<<: *build
image: debian:sid
test:testing:
<<: *test
dependencies:
- build:testing
image: debian:testing
test:unstable:
<<: *test
dependencies:
- build:unstable
image: debian:sid
That's a bit much. How does it work?
Let's look at every individual toplevel key in the .gitlab-ci.yml file:
stages:
- build
- autopkgtest
Gitlab CI has a "stages" feature. A stage can have multiple jobs, which will run in parallel, and gitlab CI won't proceed to the next stage unless and until all the jobs in the last stage have finished. Jobs from one stage can use files from a previous stage by way of the "artifacts" or "cache" features (which we'll get to later). However, in order to be able to use the stages feature, you have to create stages first. That's what we do here.
.build: &build
before_script:
- apt-get update
- apt-get -y install devscripts autoconf automake adduser fakeroot sudo
- mk-build-deps -t "apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends" -i -r
- adduser --disabled-password --gecos "" builduser
- chown -R builduser:builduser .
- chown builduser:builduser ..
stage: build
artifacts:
paths:
- built
script:
- sudo -u builduser dpkg-buildpackage -b -rfakeroot
after_script:
- mkdir built
- dcmd mv ../*ges built/
This tells gitlab CI what to do when building the ola package. The main
bit is the script:
key in this template: it essentially tells gitlab
CI to run dpkg-buildpackage
. However, before we can do so, we need to
install all the build-dependencies and a few helper things, as well as
create a non-root user (since ola refuses to be built as root). This we
do in the before_script:
key. Finally, once the packages have been
built, we create a built
directory, and use devscripts
' dcmd
to
move the output of the dpkg-buildpackage
command into the built
directory.
Note that the name of this key starts with a dot. This signals to gitlab
CI that it is a "hidden" job, which it should not start by default.
Additionally, we create an anchor (the &build
at the end of that line)
that we can refer to later. This makes it a job template, not a job
itself, that we can reuse if we want to.
The reason we split up the script to be run into three different scripts
(before_script
, script
, and after_script
) is simply so that gitlab
can understand the difference between "something is wrong with this
commit" and "we failed to even configure the build system". It's not
strictly necessary, but I find it helpful.
Since we configured the built
directory as the artifacts
path,
gitlab will do two things:
- First, it will create a
.zip
file in gitlab, which allows you to download the packages from the gitlab webinterface (and inspect them if needs be). The length of time for which the artifacts are stored can be configured by way of theartifacts:expire_in
key; if not set, it defaults to 30 days or whatever the salsa maintainers have configured (of which I'm not sure what it is) - Second, it will make the artifacts available in the same location on jobs in the next stage.
The first can be avoided by using the cache
feature rather than the
artifacts
one, if preferred.
.test: &test
before_script:
- apt-get update
- apt-get -y install autopkgtest
stage: autopkgtest
script:
- autopkgtest built/*ges -- null
This is very similar to the build
template that we had before, except
that it sets up and runs autopkgtest
rather than dpkg-buildpackage
,
and that it does so in the autopkgtest
stage rather than the build
one, but there's nothing new here.
build:testing:
<<: *build
image: debian:testing
build:unstable:
<<: *build
image: debian:sid
These two use the build
template that we defined before. This is done
by way of the <<: *build
line, which is YAML-ese to say "inject the
other template here". In addition, we add extra configuration -- in this
case, we simply state that we want to build inside the debian:testing
docker image in the build:testing
job, and inside the debian:sid
docker image in the build:unstable
job.
test:testing:
<<: *test
dependencies:
- build:testing
image: debian:testing
test:unstable:
<<: *test
dependencies:
- build:unstable
image: debian:sid
This is almost the same as the build:testing
and the
build:unstable
jobs, except that:
- We instantiate the
test
template, not thebuild
one; - We say that the
test:testing
job depends on thebuild:testing
one. This does not cause the job to start before the end of the previous stage (that is not possible); instead, it tells gitlab that the artifacts created in thebuild:testing
job should be copied into thetest:testing
working directory. Without this line, all artifacts from all jobs from the previous stage would be copied, which in this case would create file conflicts (since the files from thebuild:testing
job have the same name as the ones from thebuild:unstable
one).
It is also possible to run autopkgtest in the same image in which the build was done. However, the downside of doing that is that if one of your built packages lacks a dependency that is an indirect dependency of one of your build dependencies, you won't notice; by blowing away the docker container in which the package was built and running autopkgtest in a pristine container, we avoid this issue.
With that, you have a complete working example of how to do continuous integration for Debian packaging. To see it work in practice, you might want to look at the ola version
UPDATE (2018-09-16): dropped the autoreconf call, isn't needed (it was there because it didn't work from the first go, and I thought that might have been related, but that turned out to be a red herring, and I forgot to drop it)
'because the location in master:/ will conflict with upstream once they get a Gitlab instance and also (depending on your workflow) makes updating from https://github.com/OpenLightingProject/ola harder.
https://wiki.debian.org/Salsa/Doc#Running_Continuous_Integration_.28CI.29_tests on where to configure the "Custom CI config path" in your Salsa projects.
I wrote http://travis.debian.net/ many years ago and it has had countless improvements and contributions since then. I wonder if the idea could be ported to salsa in the sense of massively de-duplicating this across all my packages?
(Also curious why you autoreconf? Does the package not do this itself?)