RPi build tools

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à.

Per compilare servono librerie, header, un compilatore che supporti l'architettura di destinazione, per cui perché dev'essere complicato? E invece…

Ci sono diverse tecniche reperibili online per crosscompilare per Raspberry Pi.

  • Compilare direttamente su un'immagine ARM di Raspbian in emulazione con QEMU (estremamente lento, vincolato alla toolchain distribuita con Raspbian)
  • Costruire un'intera toolchain dedicata e.g. con crosstool-ng o buildroot
  • Innumerevoli progetti scaricano una sysroot (l'insieme delle librerie e degli header) precostruita da qualcuno, scaricano o compilano una versione di GCC per ARM e crosscompilano
  • Da notare che GCC stesso dev'essere compilato specificamente per ARM, il che è un po' controintuitivo secondo me, dato che concettualmente, come in LLVM/Clang, la parte specifica per l'architettura di destinazione è solo una parte dell'intero processo di compilazione (quella finale di ottimizzazione e output).

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.

RPi build tools

  • Data inizio: 24 nov. 2018
  • Data fine: 7 apr. 2020
  • Stato: completo (aggiornamenti possibili)

Sorgente

Docker hub

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.

  • progetti/5p4k/rpi-build-tools.txt
  • Ultima modifica: 2020/04/07 20:30
  • da 5p4k