I use a unique keyboard (ergodox keyboard with legendless keycaps) upon which I can put qmk firmware, mainly for the purposes of macros, layout remapping to dvorak for my main use and to easily switch to qwerty layout for video games that do not support remapping.
In order to easily update the firmware and track changes I created a CI/CD pipeline in my locally host Gitlab instance. However when I tried to run it a few days ago it failed where it had been working fine previously. Let’s see if we can figure out why and fix it.
The Setup
Let’s start first with the CI/CD pipeline to review what it does and how it works
image: qmkfm/qmk_firmware
build:
before_script:
- apt-get update -y && apt-get install -y build-essential avr-libc binutils-arm-none-eabi binutils-avr dfu-programmer dfu-util gcc gcc-arm-none-eabi git libnewlib-arm-none-eabi gcc-avr python3 unzip wget zip
- git clone --recurse-submodules https://github.com/qmk/qmk_firmware
script:
- cd qmk_firmware
- qmk setup
- /usr/bin/python3 -m pip install -r /builds/sam/ergodox_keymap/qmk_firmware/requirements.txt
- grep -o '^[^$]*' ../keymap.txt > ../output.json
- qmk compile ../output.json
- CHECKSUM=$(md5sum ../output.json)
artifacts:
name: $CHECKSUM
paths:
- qmk_firmware/ergodox_ez_base_qmk_ergodox.hex
- output.json
expire_in: 2 days
rules:
- if: $CI_COMMIT_BRANCH == "main"
changes:
- keymap.txt
So it’s set to use the latest version of the qmk firmware image, install prereqs, strip out comments from the json file (lines starting with $) and then compile into a .hex keyboard firmware file with the qmk tool.
I created this a while ago and don’t recall if it was copied from elsewhere in parts or wholly original. The point is that it worked at one time but stopped.
The Error
Now let’s review the error message we got;
$ qmk compile ../output.json
Traceback (most recent call last):
File "/usr/local/bin/qmk", line 8, in <module>
sys.exit(main())
File "/usr/local/lib/python3.7/dist-packages/qmk_cli/script_qmk.py", line 76, in main
import qmk.cli # noqa
File "/builds/sam/ergodox_keymap/qmk_firmware/lib/python/qmk/cli/__init__.py", line 252, in <module>
__import__(subcommand)
File "/builds/sam/ergodox_keymap/qmk_firmware/lib/python/qmk/cli/painter/__init__.py", line 2, in <module>
from . import make_font
File "/builds/sam/ergodox_keymap/qmk_firmware/lib/python/qmk/cli/painter/make_font.py", line 8, in <module>
from qmk.painter_qff import QFFFont
File "/builds/sam/ergodox_keymap/qmk_firmware/lib/python/qmk/painter_qff.py", line 12, in <module>
from qmk.painter_qgf import QGFBlockHeader, QGFFramePaletteDescriptorV1
File "/builds/sam/ergodox_keymap/qmk_firmware/lib/python/qmk/painter_qgf.py", line 248
def _for_all_frames(x: FunctionType, /, images):
^
SyntaxError: invalid syntax
Invalid Syntax pointing to the `/` in the arguments. I do quite a bit of python recreationally and did not recognize this as valid syntax. After a quick google it appears that this is a python feature only added with 3.8, called “Positional-Only parameters”. Essentially, everything after the / in the argument list is unordered. The PEP is good reading if you want to know more.
The CI/CD pipeline should be pulling the latest version of the image, so is that image not updated or has an older version of python? Lets find out using the Podman desktop application. I downloaded and started up a container and used the terminal to check the python version installed.
Well, there it is, seems like the qmkfm image has not been updated in a while and has an older version of python that does not support the Positional-Only Parameter syntax.
The Solution[s] (A Fork In The Road)
Now that we know what the issue is we have (at least) two ways to solve it. Update the python version to a version that supports the syntax or switch the git branch to before this change was made.
The Easy Path
Naturally I took the path of least resistance to get it back up and running so I could get my firmware updated with a new macro. Because we know the workflow was working before hand and apparently nothing has changed for the image recently it must be changes made in the qmk_firmware git repository, so let’s use the github tools to find out.
In the python stack trace above we can see that the line with the 3.8 python syntax is found in qmk_firmware/lib/python/qmk/painter_qgf.py.
Open the file in the github repository and switch to the “Blame” option
Here we can see the commit that last modified the function in question, which in turn looks like it is tied to a git tag called 'breakpoint_2024_02_25’. Based on the name of the tag alone it is probably safe to assume that a git tag prior to this one would work as expected on the out-of-date qmk docker image.
Looking at the git tags page we can see the tag immediately prior to the breakpoint is ‘0.23.9’, so let’s try updating the CI/CD pipeline to use this tag and see if it works.
To do this we simply update the git clone line to tell it what branch to clone
git clone --branch=0.23.9 --recurse-submodules https://github.com/qmk/qmk_firmware
Not a great image I could put here to show it was successful but it was! The breakpoint git tag was great! Good on the QMK maintainers for tidy git practices.
The “Correct” Path
Okay, it’s a few days later, the time crunch has passed so let’s take some time and update the CI/CD script to hopefully avoid or mitigate this issue in the future.
First, it’s clear that the qmkfm image has been effectively abandoned, so lets try to use a regular vanilla ubuntu image. I again used the podman desktop application to test with the ubuntu image. Manually going through the steps helped me to better understand what is and is not necessary and we were able to optimize the workflow a bit.
Here’s the final result;
image: ubuntu:latest
build:
before_script:
- apt-get update -y && apt-get install -y build-essential avr-libc binutils-arm-none-eabi binutils-avr dfu-programmer dfu-util gcc gcc-arm-none-eabi git libnewlib-arm-none-eabi gcc-avr python3 python3-pip unzip wget zip
- python3 -m pip install qmk
- git clone --depth=1 --recurse-submodules https://github.com/qmk/qmk_firmware
script:
- cd qmk_firmware
- grep -o '^[^$]*' ../keymap.txt > ../output.json
- qmk compile ../output.json
- CHECKSUM=$(md5sum ../output.json)
artifacts:
name: $CHECKSUM
paths:
- qmk_firmware/ergodox_ez_base_qmk_ergodox.hex
- output.json
expire_in: 2 days
rules:
- if: $CI_COMMIT_BRANCH == "main"
changes:
- keymap.txt
So by switching to a generic ubuntu image and actually reviewing the steps we were able to remove the qmk setup and we’re installing qmk via pip instead of relying on it being installed as part of the qmk docker image. No real speed improvements for the pipeline and it could likely be further optimized, especially with the apt-get packages being installed but it works and I optimize for that over most other things.