Immagini Docker per cross-compilare per il Raspberry Pi.
Documentazione nel sorgente, guida all'uso nel Docker hub.
Ho un altro progetto per il Raspberry Pi, pwmpi, che però ho scritto in C++17, quando il massimo che c'era sul Raspberry Pi era gcc 4.7. Da lì, la necessita di cross-compilare per il Raspberry Pi con compilatori più recenti. Adesso sul Raspberry Pi ci sono compilatori decisamente più aggiornati, tuttavia la convenienza di precompilare (cross-compilare, in questo caso) resta imbattuta, soprattutto per semplicità.
Ci sono diverse tecniche reperibili online per crosscompilare per Raspberry Pi.
Per cui ho cercato di ripercorrere la strada più diretta, creando una sysroot a mano e usando Clang invece di Gcc come compilatore sul sistema host.
All'inizio ero abbastanza confuso sulla differenza tra Raspbian e Debian, entrambi con tecnologia armhf
. Sostanzialmente, Raspbian è compilato apposta per ARMv6, mentre lo standard armhf
di Debian è in realtà ARMv7. Suppongo che funzioni fondamentalmente su tutte le periferiche eccetto il Raspberry Pi 1. Ad ogni modo scovato l'inghippo, ho reperito anche le compile flags per Raspbian.
Quale sysroot migliore di quella direttamente installata sul sistema operativo? Sfruttando la capacità di apt
di scaricare pacchetti di varie architetture, ho assemblato uno script che scarica (in un'immagine Debian x86_64) i pacchetti di Raspbian e li scompatta in una cartella. Con un po' di trial and error, su un'immagine Docker con QEMU e su un vero Raspberry Pi, ho ridotto la lista dei pacchetti necessari per una sysroot a 15 soltanto (11 senza libc++). Eseguibili e archivi compressi si possono scartare; forse anche di più. Sostanzialmente rimangono librerie ed header.
Originariamente, nell'interesse di ottenere una soluzione rapida, invece di scompattare i .deb
di Raspbian, avevo pensato a costruire una sysroot basata su LLVM, crosscompilando tutti i vari componenti di LLVM. D'altronde, Clang sarebbe stato comunque il compilatore scelto, per cui perché non usare l'intero progetto? Tuttavia questo al momento non è possibile su ARMv6, perché compiler-rt
(che genera files come crtbegin.o
, che vengono segretamente linkati dentro tutti i programmi C++, dato che contengono parti di runtime necessario) non supporta ARMv6 correttamente.
Installata la sysroot in /usr/share/rpi-sysroot
, installato Clang su una distro (Debian, ma anche Alpine), dovrebbe essere relativamente semplice, dato che Clang su Linux di default supporta anche ARM. Non è proprio così, perché bisogna dire al compilatore dove trovare gli header. A quanto pare, la “procedura standard” per reperire la libreria standard del C e del C++, consiste nel… testare i percorsi che sono stati specificati al momento in cui il compilatore stesso è stato compilato. Sembra che questi vengano hardcodizzati nel compilatore. La maniera migliore, è dargli soltanto la sysroot, e sperare che Clang ce la faccia a trovarli. In realtà ce la fa a trovare la libreria standard del C++, ma non quella del C. Per quella è servito un bel po' di trial and error finché non ho trovato un percorso unico da passare che non rompesse le dipendenze nascoste tra i vari header.
Qui da notare, su Debian il problema non era emerso perché Clang utilizzava gli header installati sul sistema host stesso (!) automaticamente. Per fortuna, cercare una soluzione minimale è utile, perché Alpine si è immediatamente rifiutato di compilare. I risultati delle peripezie sono in realtà poche righe di CMake. Inestimabile la documentazione di CMake su che variabili usare.
Questo merita una menzione a parte, perché anche linkare non può esser fatto con ld
, ma dev'essere fatto ovviamente con la controparte di LLVM, lld
(naming…). Per questo basta passare -fuse-ld=lld
' al linker, ma attenzione! Questo si applica soltanto a librerie standard, librerie dinamiche ed eseguibili. Per le librerie statiche a quanto pare, l'assemblaggio del binario finale è fatto con ar
, per cui nel caso di CMAKE_STATIC_LINKER_FLAGS
, non c'è bisogno di specificare LLD.