
Main ingredient
The thing that prevented me from adding CI in the past was Arduino IDE. Yup, that window hungry and toolchain concealing tool, which I had no idea how to cross breed with windowless containerized build server. However, Arduino IDE’s got its younger brother now –arduino-cli
, which sounds like a silver bullet for my problem. If it does what it says, I’ll simply need to draft a good old .gitlab-ci.yml
, point it to GitLab CI’s docker executor, install and configure arduino-cli
, download the same libraries that I used with Arduino IDE and voila!
I won’t get into details about how GitLab CI itself works. I also won’t describe its runners and especially the ones that use docker. That’s just too much info, which already was written about.
Instead, will start right at the point where GitLab CI already exists and it has properly configured docker runner tagged with the word docker
. So the obvious first step is to create .gitlab-ci.yml
file.
.gitlab-ci.yml
Here’s what our.gitlab-ci.yml
should do:
- Request a build in docker container
- When in there, install
arduino-cli
and all imaginable dependencies - After that, build project
1. Request a build in docker executor
.gitlab-ci.yml
can target specific GitLab CI runner by specifying its tag. As I mentioned before, our docker runner is tagged by docker
, so the same tag should be mentioned CI’s build step.
Another thing is that I don’t remember what docker image that runner uses by default. In fact, that’s a good thing, as being explicit about ones expectations is a virtue in many contexts. The project’s ‘home’ OS is Ubuntu, so let’s request ubuntu:18.04
image for a build as well.
Having said that, here’s what .gitlab-ci.yml
starts to look like:
1 2 3 4 5 6 7 8 9 10 11 |
image: ubuntu:18.04 stages: - build compile: stage: build tags: - docker script: - echo "Something like compilation should go here" |
2. Installing arduino-cli and its dependencies
2.1 Setting up arduino-cli
arduino-cli
is just a downloadable archive, so a few lines of bash script can be called ‘an installation’. However, after these few bash lines many other will follow, so let’s create a new shell file and put everything in there. I’ll call it setup-build-env.sh
:
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/bash apt-get update cd ~ # Install arduino-cli apt-get install curl -y curl -L -o arduino-cli.tar.bz2 https://downloads.arduino.cc/arduino-cli/arduino-cli-latest-linux64.tar.bz2 tar xjf arduino-cli.tar.bz2 rm arduino-cli.tar.bz2 mv `ls -1` /usr/bin/arduino-cli |
sudo
‘s, because 1) most likely it’s already going to run as root
in Docker container and 2) even if it’s not, it’s easier to add one sudo
before the file name than one before every other line in the file itself.
2.2 Setting up ESP32 support
Now the fun part begins. I remember that in order to add ESP32 support to Arduino IDE I had to open some board manager window and copy-paste mysterious URL. Apparently, the same stuff needs to be done here, but this time through cli.
1 2 3 4 |
# Install esp32 core printf "board_manager:\n additional_urls:\n - https://dl.espressif.com/dl/package_esp32_index.json\n" > .arduino-cli.yaml arduino-cli core update-index --config-file .arduino-cli.yaml arduino-cli core install esp32:esp32 --config-file .arduino-cli.yaml |
.arduino-cli.yaml
file. The name has no special meaning, it’s just the first one that came into my mind. I believe arduino-cli
does have some default file name, but I don’t trust defaults in a tool that’s still released in ‘preview’ edition.
The last line installs magical esp32:esp32
, which I learned about by executing arduino-cli core search esp32 --config-file .arduino-cli.yaml
.
Having ESP32 core installed, we can, for instance, run another command – arduino-cli board listall
– and find out the identifier of the board for which we’ll compile the project. I’m using a ESP32 Dev Board equivalent, whose fully qualified board name appears to be esp32:esp32:esp32
.
1 2 3 4 5 6 7 |
arduino-cli board listall # ... # Dongsen Tech Pocket 32 esp32:esp32:pocket_32 # ESP32 Dev Module esp32:esp32:esp32 # ESP32 FM DevKit esp32:esp32:fm-devkit # ESP32 Pico Kit esp32:esp32:pico32 # ... |
2.3 Installing standard packages
It took me some time, but finally I came up with the list of Arduino libraries that I installed over the past couple of months. Now we’ll need that list to reinstall those libraries forarduino-cli
.
1 2 3 4 5 6 |
# Install 'native' packages arduino-cli lib install "Adafruit BME280 Library" arduino-cli lib install "Adafruit Unified Sensor" arduino-cli lib install "HCSR04 ultrasonic sensor" arduino-cli lib install "ArduinoJson" arduino-cli lib install "MPU9250_asukiaaa" |
2.4 Installing non-standard packages
Along with standard Arduino packages I also used a number of Github projects which I cloned into Arduinolibraries
folder. If we find where arduino-cli
stores its stuff, we could git clone
them again.
Fortunately, arduino-cli config dump
can tell us exactly where:
1 2 3 4 5 |
arduino-cli config dump # proxy_type: auto # sketchbook_path: /root/Arduino # arduino_data: /root/.arduino15 # board_manager: {} |
1 2 3 4 5 6 7 8 |
# Install 'third-party' packages: find proper location and 'git clone' apt-get install git -y cd `arduino-cli config dump | grep sketchbook | sed 's/.*\ //'`/libraries # e.g. /root/Arduino/libraries git clone https://github.com/me-no-dev/AsyncTCP.git git clone https://github.com/me-no-dev/ESPAsyncWebServer.git git clone https://github.com/ThingPulse/esp8266-oled-ssd1306.git git clone https://github.com/RoboticsBrno/ESP32-Arduino-Servo-Library.git cd - |
2.5 Setting up python and pyserial
If I’m not mistaken, these two are required by ESP32 IDF – the native ESP32 toolchain that Arduino runs under the hood. Whether or not that is true, withoutpython
and pyserial
arduino-cli
refuses to compile ESP32 boards, so we’ll need them anyway:
1 2 3 4 5 |
# Install python, pip and pyserial apt-get install python -y curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py pip install pyserial |
2.6 …and putting it all together
Putting all these pieces intocreate-build-env.sh
file, here’s what we get in the end:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/bin/bash apt-get update cd ~ # Install arduino-cli apt-get install curl -y curl -L -o arduino-cli.tar.bz2 https://downloads.arduino.cc/arduino-cli/arduino-cli-latest-linux64.tar.bz2 tar xjf arduino-cli.tar.bz2 rm arduino-cli.tar.bz2 mv `ls -1` /usr/bin/arduino-cli # Install python, pip and pyserial apt-get install python -y curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py pip install pyserial # Install esp32 core printf "board_manager:\n additional_urls:\n - https://dl.espressif.com/dl/package_esp32_index.json\n" > .arduino-cli.yaml arduino-cli core update-index --config-file .arduino-cli.yaml arduino-cli core install esp32:esp32 --config-file .arduino-cli.yaml # Install 'native' packages arduino-cli lib install "Adafruit BME280 Library" arduino-cli lib install "Adafruit Unified Sensor" arduino-cli lib install "HCSR04 ultrasonic sensor" arduino-cli lib install "ArduinoJson" arduino-cli lib install "MPU9250_asukiaaa" cd - # Install 'third-party' packages: find proper location and 'git clone' apt-get install git -y cd `arduino-cli config dump | grep sketchbook | sed 's/.*\ //'`/libraries git clone https://github.com/me-no-dev/AsyncTCP.git git clone https://github.com/me-no-dev/ESPAsyncWebServer.git git clone https://github.com/ThingPulse/esp8266-oled-ssd1306.git git clone https://github.com/RoboticsBrno/ESP32-Arduino-Servo-Library.git cd - |
.gitlab-ci.yml
and call just before the build.
1 2 3 4 5 6 7 8 |
image: ubuntu:18.04 before_script: - ./setup-build-env.sh stages: - build # ... |
3. Finally, the build step
So the only missing step is the compilation itself. I already know fully qualified board name, I know how to spell the wordcompile
, so putting it all together and pointing it to a project file called robot.ino
, here’s what build line ends up being:
1 |
arduino-cli compile --fqbn esp32:esp32:esp32 -o ignored.bin robot.ino |
PROFIT!
This is the final.gitlab-ci.yml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
image: ubuntu:18.04 before_script: - ./setup-build-env.sh stages: - build compile: stage: build tags: - docker script: - arduino-cli compile --fqbn esp32:esp32:esp32 -o ignored.bin robot.ino |



I guess ‘platformio’ is much simpler to use. But thx I’ve learnd alot about “arduino-cli”.
json
image: python:2.7
stages:
- build
before_script:
- "pip install -U platformio"
job:
stage: build
script: "platformio run"