NixOS et STM32Cube, une histoire d'amour complexe (lol)
2026-04-13 (updated: 2026-04-13 )
Disclaimer: jsuis pas très bon sur Nix je galère encore pas mal. Le but du jeu là c’était pas d’avoir un packaging dans la philosophie de nix tout beau tout pure, mais plutôt d’avoir un truc qui marche et qui soit pas trooop long à refaire. On peut sûrement faire bien mieux que ça. D’ailleurs je suis tombé après coup sur ce post qui le fait un peu plus proprement (mais ne rentre pas trop dans les détails…).
Kézako ?
Au taff je travaille avec des board de STMicroelectronics qui tournent avec des microcontrolleurs STM32 (de la famille ARM M0+) pour des applications LoRa embarquées low power. A la base j’avais reprit le projet de quelqu’un qui avait fait son maximum pour se défaire de la toolchain ST, j’avais donc continué dans cette lancée en réussissant à totalement me défaire des outils de ST (au prix d’embarquer leur compilo ARM modifié dans une archive git …).
C’était avec le recul une mauvaise idée. J’ai un peu d’expérience avec pas mal de build systems (Makefile, CMake et Cargo entre autre) mais je n’avais jamais vraiment fait d’embarqué; on peut donc légitimement remettre en question l’idée de se passer des petites roulettes sur le vélo alors qu’on sait même pas pédaler…
Concrêtement, j’avais une toolchain brouillon à base d’un saint Makefile qui: build le firmware, des outils rust, utilise les outils rust pour flash les cartes etc. Aucun débug, aucune facilité à remonter un nouveau projet et des Makefile modifiés plusieurs fois à la main pour ajouter HAL (Hardware Abstraction Layer) et bibliothèques de code là où j’aurai pu (et du) utiliser les outils ST.
Le ponpon c’était vraiment le fait de pas pouvoir debug. Avec les années j’ai appris à me passer des printf lâchés un peu au pif dans le code pour debug à peu près convenablement. J’aime bien GDB malgré ses torts, et dire qu’il me manquait serait un euphémisme. De plus, aucun moyen d’inspecter la mémoire ou les registres du MCU, pas de pas-à-pas etc. bref l’enfer surtout en embarqué.
Keskifo ?
Mais alors de quels outils ais-je besoin ? Concrètement j’aurai bien voulu faire marcher:
- STM32CubeIDE, l’IDE développé par ST au dessus d’Eclipse. Même si c’est vraiment pas top pour coder, j’avoue que c’est vraiment pas mal pour debug et ça s’intègre à la toolchain assez facilement. Avec un plugin vim ça devient à peu près supportable, puis jpeux toujours utiliser mon neovim en dehors et juste garder CubeIDE pour le débug.
- STM32CubeMX, pour scaffholder des projets. Pour l’instant j’en ai pas eu besoin mais à l’avenir ça peut servir.
- STM32CubeProgrammer, pour pouvoir flasher / debug des boards sans passer par l’IDE; Plus pratique pour simplement inspecter du registre là où l’IDE je le garde plus pour le pas à pas.
Et du coup ?
Du coup je tombe pile dans un cas d’usage ou Nix peut très vite devenir chiant. J’ai des programmes:
- Pas packagés pour Nix
- Propriétaires
- Vraiment pas adaptés pour un packaging nix (ils aiment bien écrire un peu n’importe ou, embarquent 50 dépendences par outil etc.)
Et faut savoir que j’ai jamais packagé pour nix, alors patcher un truc aussi complexe c’était clairement pas une idée qui m’attirait pour une première…
J’ai donc été au plus simple: faire un FHSEnv. Vous savez le FHS (Filesystem Hierarchy Standard) c’est ce truc qui définit l’architecture d’une arborescence sous un système Linux. Les binaires vont là (/bin, /usr/bin, …) les librairies partagées vont ici (/lib, /usr/lib, …) etc. Bah c’est notamment là que NixOS s’est placé en rupture, avec l’utilisation du Nix Store et un système de fichier basé sur l’isolation et largement en lecture seule. Cependant, pour gérer les cas où on a pas trop le choix que de respecter le FHS car le logiciel qu’on utilise assume que c’est la norme, alors Nix nous offre FHSEnv.
Concrètement, FHSEnv permet de monter un filesystem root, un peu comme si je chrootais sur un système FHS compliant mais sans s’embêter. J’ai donc commencé par récupérer les outils de ST qui viennent sous la superbe forme de…. de .sh qui contiennent tout le package et font un peu ce qu’ils veulent (pour certains, d’autres c’est un exécutable fin bref aucune cohérence)! Le but du jeu maintenant c’est de réussir à obtenir un environnement sommaire qui permette d’installer et faire tourner (à peu près) les outils ST. En tatônnant et en utilisant un peu mon copain Gemini j’en suis arrivé à cette liste de packages:
targetPkgs = pkgs: with pkgs; [
# X11 related
libx11 libxext libxrender libxtst libxi libxxf86vm
# gtk related
gtk3 webkitgtk_4_1 libsoup_3 glib pango cairo gdk-pixbuf
# ogl / font / io related
alsa-lib fontconfig freetype mesa libGL
# extra related
zlib nss nspr expat
ncurses5 libxcrypt-legacy psmisc
# st-link related
krb5 libusb1 libudev-zero
]
Avec ça, j’arrive à lancer les installeurs et tout installer dans un dossier bidon qui me servira plus tard. Seul point qui pêche: impossible d’installer stlink-server avec le script d’installation de STM32CubeIDE. J’ai donc un environnement qui marche mais aucune capacité de Debug. En gros dit autrement j’ai juste un moins bon environnement ^^.
Cependant je garde espoir. En récupérant stlink-server sur le site de ST j’arrive à extraire l’exécutable du script d’installation. Manque plus qu’à en faire une dérivation pour placer l’exécutable dans le path et houra j’arrive à débug!! Sérieusement la première fois que ça a marché (enfin la deuxième, la première j’avais foiré une config côté application en oubliant d’activer le débug et surtout désactiver le low power) j’ai failli chialer.
Cependant j’ai devant moi un sacré bordel. Pour accéder à mon environnement je dois:
- Choper les soft et les installer / extraire.
- Faire un coup de
nix developqui prend 15 jours parce qu’il a trop de chosesà foutre dans son store, et panique à chaque modif mal indexée. - Aller lancer les exécutables à la main.
En somme c’est mieux mais c’est pas top
Et udev ??
Petite appartée, il faut activer des règles udev spécifiques à STLink. Personnelement je les avait déjà activées avec probe-rs, mais sinon sous NixOS il faut rajouter services.udev.packages = [ pkgs.stlink ]; à votre configuration.nix, ou les rajouter à la flake si vous êtes du genre à faire les choses proprement (j’ai opté pour une install system-wide parce que je ne fais pas parti de cette team! et surtout parce que j’ai perdu bien trop de temps déjà).
Faites mieux !
Et si on se faisait des petites tarball avec les outils de ST et qu’on ajoutait ces tarball au store de Nix pour les utiliser dans la flake ?
Pour ça c’est simple: on fait les tarball, on chope leur hash avec nix-hash et on les ajoute au store avec nix-store. Ensuite on fait une ptite fonction pour gérer les dérivations plus proprement (DRY!!! mdr) et finalement on peut avoir un flake qui marche et qui est (un peu plus) reproducible ci tant est qu’on a les tarballs. Ca fait à peu près 1Go d’archive, c’est pas fou mais franchement j’ai déjà vu bien pire… Et on peut lancer les outils depuis le terminal sans aller taper le chemin de l’exécutable. To me it’s a win. J’y serai jamais arrivé sans IA (fin si mais ça aurait impliqué de poser des questions sur les forums et attendre désespérément une réponse) et ça me fait un peu chier mais c’est déjà ça ! Et jpense que tant que tu comprends ce qu’il se passe c’est pas siiii grave d’avoir eu un peu d’aide de nos amis linéo-algébriques…
On pourrait aller un chouilla plus loin en utilisant les installeurs de ST directement dans notre flake plutôt que de passer par des tarball intermédiaires mais on verra plus tard hein…. Ou on pourrait se donner à patcher tout pour nix mais c’est certainement pas moi qui vais m’en charger…
Voilà c’est déjà tout. J’ai rédigé ce post un peu à la va-vite pour qu’il me serve de mémo et puisse éventuellement aider un dev en galère… N’hésitez pas à m’écrire pour plus d’explications / si j’ai dit des grosses bétises (codeberg-contact.ventricle108@passmail.net)!
Flake Complète
{
description = "STM32 toolchain";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
mkStToolArchive = { name, archiveName, url, sha256, exec, execName }: pkgs.stdenvNoCC.mkDerivation {
inherit name;
nativeBuildInputs = [ pkgs.makeWrapper ];
src = pkgs.requireFile {
name = archiveName;
inherit url;
inherit sha256;
message = ''
The asset ${archiveName} is missing from the Nix store.
Please create it by compressing a linux-agnostic ${name} filetree
`tar -czvf ${archiveName} -C /path/to/${name} .`
And add it to the Nix store
`nix-prefetch-url file://\$PWD/${archiveName}`
'';
};
dontConfigure = true;
dontBuild = true;
sourceRoot = ".";
installPhase = ''
# creating tool dir and bin dir (if not already created)
mkdir -p $out/opt/${name} $out/bin
# copying from extraction dir to final tool dir
cp -a ./* $out/opt/${name}
chmod +x "$out/opt/${name}/${exec}"
# construct absolute path wrapper
makeWrapper "$out/opt/${name}/${exec}" "$out/bin/${execName}"
runHook postInstall
chmod +x "$out/bin/${execName}"
'';
};
cube-mx = mkStToolArchive {
name = "CubeMX";
archiveName = "cubemx.tgz";
sha256 = "00iv83xp731rhi9g0lpzgk10l0ja13kir6x6n80hm0dp6g3fya4r";
url = "https://www.st.com/en/development-tools/stm32cubemx.html";
exec = "STM32CubeMX";
execName = "cube-mx";
};
cube-programmer = mkStToolArchive {
name = "CubeProgrammer";
archiveName = "cubeprogrammer.tgz";
sha256 = "0il722jhhpa8l5kzjcxv9cxnfr3k7fd223rny3zv09x1jilv2ah6";
url = "https://www.st.com/en/development-tools/stm32cubeprog.html";
exec = "bin/STM32CubeProgrammer";
execName = "cube-programmer";
};
cube-ide = mkStToolArchive {
name = "CubeIDE";
archiveName = "cubeide.tgz";
sha256 = "16rfbsvpkzczmssvk1k110rxqfcyg0k8snr22jjwjrhh6k1av0xh";
url = "https://www.st.com/en/development-tools/stm32cubeide.html";
exec = "stm32cubeide";
execName = "cube-ide";
};
stlink-server = mkStToolArchive {
name = "stlink-server";
archiveName = "stlink.tgz";
sha256 = "0dhmlrbl0kymhk3xh0klabwgynsc5cl427f96y1lcdkz5rks8wix";
url = "https://www.st.com/en/development-tools/st-link-server.html";
exec = "stlink-server";
execName = "stlink-server";
};
cube-fhs = pkgs.buildFHSEnv {
name = "cube-env";
targetPkgs = pkgs: with pkgs; [
# X11 related
libx11 libxext libxrender libxtst libxi libxxf86vm
# gtk related
gtk3 webkitgtk_4_1 libsoup_3 glib pango cairo gdk-pixbuf
# ogl / font / io related
alsa-lib fontconfig freetype mesa libGL
# extra related
zlib nss nspr expat
ncurses5 libxcrypt-legacy psmisc
# st-link related
krb5 libusb1 libudev-zero
# toolchain
cube-mx cube-programmer cube-ide stlink-server
];
profile = ''
# force X11 backend to prevent Wayland blank-screen issues in JavaFX
export GDK_BACKEND=x11
export _JAVA_AWT_WM_NONREPARENTING=1
# redirect system prefs to a writable user directory to prevent Unix Error Code 2
mkdir -p ~/.java/.systemPrefs
export JAVA_TOOL_OPTIONS="-Djava.util.prefs.systemRoot=$HOME/.java -Djava.util.prefs.userRoot=$HOME/.java"
'';
runScript = "zsh"; # if you ctrl+c/ctrc+v you might wanna make sure ure using zsh as well ^^
};
in {
devShells.${system}.default = pkgs.mkShell {
packages = [ cube-fhs ];
shellHook = "cube-env";
};
};
}