I'm hosting my personal projects on Gitlab. Until now, I was mainly using it as a git remote repository, without using the other Gitlab features.
For one of my projects, a port of hode (Heart of Darkness engine) for the RG-350 retro gaming handheld, I used to build releases on my own dev machine. Which is not really the state-of-the-art of releasing software. So I decided to have a look at the Gitlab CI/CD feature, in order to use it to build my project.
It's not really complicated to setup, but I'd like to share a concrete and rather simple build case. The examples shown below will be simplified a bit to focus on what matters.
How it works
Every action performed by gitlab CI/CD system is described in a .gitlab-ci.yml
file at the root of the project. This file fully determines your pipeline: it's a set of jobs (like build, test or deploy) that are run in different stages. The stages determine the job order. Several different jobs can belong to the same stage, and will be run in parallel. For instance, you could have a test
stage that runs per-platform test jobs (for multi-platform projects), or per-browser test jobs for web projects. More details here.
By default, every time you push a commit, your pipeline will be run, given there is a valid .gitlab-ci.yml
, and there are Runners enabled in your project. By default, Gitlab offers shared runners for a limited amount of time per month.
In this article, we will make a single stage, single job pipeline, just to build our project.
Let's go
So, first, we just create a .gitlab-ci.yml
file a the root of your sources. It can be better to work in a separate branch, since it's a lot of trial and error before making it green.
Specifying a Docker image
In order to better specify the Runner environment, I chose to use a Docker image. It's done like this:
image: ubuntu:20.04
Preparing the build
My project requires cmake and build tools, which need to be available in the Runner. So, let's create a job that installs these tools with apt-get
.
image: ubuntu:20.04
prepare:
stage: .pre
script:
- apt-get -y update && apt-get -y install --no-install-recommends cmake gcc make
Here I created a prepare
job, that runs with stage .pre
. This job will execute the specified script.
.pre
is a predefined stage that runs first. One can define any stage and the order to run them thanks to the stages
keyword.
Building
Now, we can create the actual build job. Let's add the following at the end of our file:
build:
stage: build
script:
- cmake .
- make
This is a simple build
job that runs in the build
stage (that is also a predefined stage). Easy right? Since Runners automatically checkout the git repository runs script in the project directory, we just have to run cmake
and make
in our build script.
Here is what I got when the CI ran:
Using docker image sha256:4dd97cefde62cf2d6bcfd8f2c0300a24fbcddbe0ebcd577cc8b420c29106869a for ubuntu:20.04 with digest ubuntu@sha256:b4f9e18267eb98998f6130342baacaeb9553f136142d40959a1b46d6401f0f2b ...
$ cmake .
/usr/bin/bash: line 121: cmake: command not found
Cleaning up file based variables 00:01
ERROR: Job failed: exit code 1
Not good. Nope. So what happened here? I did install those packages, right? It turns out that different jobs don't run in the same environment. So all the packages installed in my prepare
job are not here (and they never were!). To solve this issue, I see 2 solutions:
- Build your own Docker image, that bundles the required tools. Then publish it on the Docker hub registry, and reference it from your YAML file.
- Do everything in the build job. Since it's the more straightforward solution, that's what I did!
Now, our file looks like this:
image: ubuntu:20.04
build:
stage: build
before_script:
- apt-get -y update && apt-get -y install --no-install-recommends cmake gcc make
script:
- cmake .
- make
Now, each time I push a commit, it's built by the CI!
When the job runs, before_script
and script
instructions are merged and executed sequentially. Using before_script
is just a way to split the environment setup from the actual build.
Some tips
- As I said, the Runner automatically clones the git repository. It's done in the
/builds/<organization>/<project>
directory. It's the default directory, and it's set in the$CI_PROJECT_DIR
environment variable. - Don't hesitate to use moulte
echo
commands to debug. - Read the doc!
- See the examples.
Final thoughts
I really like the simplicity of the definition of a CI with Gitlab. At the same time, it seems rather powerfull, but I didn't try a more advanced configuration yet. Runners can take several minutes to spawn, but most of the time, I had my CI running immediately after pushing a commit, wich is rather satisfying.
On the other hand, I found the documentation a bit unclear. I think it particularly lacks information on the Runner environments, and how they work under the hood. It would help understanding most errors. By reading the different documentation pages and examples, I also noticed several ways to do the same thing, but it was not clear which one was recommended.
That's all for now, happy hacking!
Photo by Dayne Topkin on Unsplash