Armv6 support#761
Conversation
Restores support for ARM1176JZF-S (ARMv6 + VFPv2) devices, dropped in 345f15c (Feb 2022). Modern librespot (0.8.0) compiles and runs on ARMv6; the obstacles were not librespot's source but two toolchain details: - ring (rustls' crypto crate) ships ARMv7/NEON assembly that ignores -march=armv6 and faults on an ARM1176. Fixed by building with the native-tls (OpenSSL) backend instead of rustls, which drops ring. - Debian's armhf cross-toolchain is ARMv7-baseline; its crt1.o has a Thumb-2 _start that SIGILLs on an ARM1176 before main runs. Fixed by cross-compiling against a Raspberry Pi OS (Raspbian) ARMv6 sysroot and linking with its v6 startfiles. With both fixes, stock stable Rust produces a binary tagged v6 / Thumb-1 / VFPv2 that runs cleanly (verified under QEMU's strict arm1176 model). Changes: - Dockerfile.armv6: separate builder with a Raspbian ARMv6 sysroot (libasound2/libpulse/libssl), sysroot symlink fixups, and staged v6 startfiles so Debian's ARMv7 crt1.o is not substituted. - build.sh: build_armv6() (arm-unknown-linux-gnueabihf, native-tls, sysroot link flags); parameterized features and DEB_ARCH; best-effort final chown. - Makefile: builder_armv6 + armv6 targets. - control.debian.tmpl: Architecture from DEB_ARCH (RPi OS reports armhf for ARMv6). - install.sh: detect ARMv6 and point to the standalone .deb. - ARMV6.md: investigation, root causes, build instructions, caveats (performance unverified; not served via the shared apt repo). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ARMv6 build uses native-tls (OpenSSL), but Raspberry Pi OS ships different OpenSSL sonames per release (bullseye: libssl.so.1.1; bookworm/trixie: libssl.so.3). A dynamically linked build therefore only runs on the release whose sysroot it was built against (e.g. it fails on trixie with "libssl.so.1.1: not found"). Link OpenSSL statically (OPENSSL_STATIC=1 + explicit OPENSSL_LIB_DIR/ INCLUDE_DIR into the sysroot) so a single .deb works across Raspberry Pi OS releases. All other shared deps have stable sonames present on every current release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Its tested on a Pi Zero v1.3 as fully functional with these changes. |
|
Thanks for contributing. Quite a feature there. Now there are a few issues with this approach that makes me skeptical of merging:
Also the documentation is agentic and verbose but also incomplete, but still complains about caveats and performance tests. It would benefit from being cleaned up massively. |
Address PR review feedback: - Link OpenSSL dynamically instead of statically so OpenSSL CVEs are fixed by an OS apt upgrade rather than a Raspotify rebuild. build.sh detects the sysroot's libssl soname and sets the matching package dependency (libssl1.1 on bullseye, libssl3 on bookworm/trixie). - Parameterize the Raspbian suite (RASPBIAN_SUITE, default bullseye) so per-soname builds are a one-flag change. - Remove ARMV6.md; move the build rationale into Dockerfile.armv6 (the riscv64 builder has no companion doc either) and the install steps into README.md, which previously said ARMv6 was unsupported. - Trim the Dockerfile comments to the non-obvious linker mechanics. Verified: builds clean, the .deb depends on libssl1.1 and links libssl.so.1.1/libcrypto.so.1.1 dynamically, and the binary runs under ARMv6 emulation (librespot --version, Tag_CPU_arch v6 / VFPv2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Sure - first step was to see if there was any attention on this - which has now been confirmed :) I've cleaned it up a bit now according to your comments - lemme hear what you think of it. In terms the out-of-band distribution/installation: Raspberry Pi OS reports ARMv6 as armhf, the identical architecture string the ARMv7 build already uses, so apt can't distinguish the two packages in one repository |
| @@ -79,7 +99,7 @@ for dp, _, fs in os.walk(root): | |||
| rel = os.path.relpath(os.path.join(root, t.lstrip("/")), dp) | |||
| os.remove(p) | |||
| os.symlink(rel, p) | |||
| print("sysroot symlinks relativized") | |||
| print("sysroot prepared") | |||
| PY | |||
There was a problem hiding this comment.
I reckon that if such an abomination is needed, then it is the wrong approach. Nevermind that this could probably just be a bash script, but manually hacking paths just feels plain wrong to me.
There was a problem hiding this comment.
have a look at the new structure. I've cleaned it up.
| ### Pi v1 and Pi Zero v1.x (ARMv6) | ||
|
|
||
| ARMv6 is supported via a standalone `.deb`. It can't be served from the apt repo or the `armhf` link above because Raspberry Pi OS reports ARMv6 as `armhf` — the same as the ARMv7 build — so the two can't be told apart by apt. Download `raspotify_*_armv6.deb` from the [releases page](https://github.com/dtcooper/raspotify/releases) and install it manually: | ||
| ARMv6 is supported on Raspberry Pi OS **bookworm and newer** via a standalone `.deb`. It can't be served from the apt repo or the `armhf` link above because Raspberry Pi OS reports ARMv6 as `armhf` — the same as the ARMv7 build — so the two can't be told apart by apt. Download `raspotify_*_armv6.deb` from the [releases page](https://github.com/dtcooper/raspotify/releases) and install it manually: |
There was a problem hiding this comment.
Are you sure this can't be worked around somehow?
There was a problem hiding this comment.
AFAIK, no. Raspberry Pi OS uses armhf as the dpkg architecture for both its ARMv6 (Pi 1 / Zero) and ARMv7+ hardware - the CPU level isn't encoded in the package arch, so apt has no way to distinguish the ARMv6 build from the existing ARMv7 armhf build in a single repo. Whichever was published last would get served to both, and the ARMv7 build SIGILLs on a Pi 1. The only "workarounds" are a separate suite/repo or a renamed package, both of which add more surface than just shipping a standalone .deb. Hence the manual-download note, which is probably fine for the subset of users who still run on old, but fully functional hardware.
| DEB_EXTRA_DEPENDS=", libssl3t64 (>= 3.0.0) | libssl3 (>= 3.0.0)" | ||
| elif [ -e "$OPENSSL_LIB_DIR/libssl.so.1.1" ]; then | ||
| DEB_EXTRA_DEPENDS=", libssl1.1 (>= 1.1.1)" | ||
| else |
There was a problem hiding this comment.
Not very happy about this kind of thing either, it adds more complexity.
There was a problem hiding this comment.
If/else removed - but I dont know what else can be done here for ARMv6.
Address review feedback on the ARMv6 build: - Replace the embedded Python sysroot-prep script with an equivalent POSIX shell block (drop static glibc .a, recreate glibc stub symlinks, relativize the sysroot's absolute symlinks via find + realpath --relative-to). Drops the python3 builder dependency. The absolute symlinks must be re-rooted into the sysroot, otherwise ld follows them against the host / and the link fails (cannot find -lm). - Collapse the per-release libssl soname detection into a single fixed dependency (libssl3t64 | libssl3). bookworm is the documented floor, so the libssl.so.1.1/bullseye branch was dead code. - Update the stale Makefile/Dockerfile comments that still implied bullseye support and soname auto-detection. Verified: make armv6 builds a v6/VFPv2 binary that dynamically links libssl.so.3, with Depends: libssl3t64 | libssl3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kimtore
left a comment
There was a problem hiding this comment.
tl;dr code readability must be dramatically improved. Claude's documentation is garbage and must be replaced entirely by something readable.
| # Prepare the sysroot for cross --sysroot linking: | ||
| # - Drop the static glibc archives (.a): they reference loader internals and | ||
| # fail to link, so force ld onto the shared libs. | ||
| # - glibc >= 2.34 folded pthread/dl/rt/util/anl/resolv into libc and dropped | ||
| # their unversioned dev symlink, so `-lpthread` etc. stop resolving. Recreate | ||
| # it pointing at the empty runtime stub; --as-needed keeps it out of NEEDED. | ||
| # - Relativize absolute symlinks (re-rooted into the sysroot): ld otherwise | ||
| # follows e.g. libm.so -> /lib/.../libm.so.6 against the host / (cannot find -lm). | ||
| RUN set -eu; \ | ||
| for d in usr/lib/arm-linux-gnueabihf lib/arm-linux-gnueabihf; do \ | ||
| dir="${RPI_SYSROOT}/$d"; \ | ||
| [ -d "$dir" ] || continue; \ | ||
| ( cd "$dir" && rm -f libpthread.a libdl.a librt.a libm.a libc.a libutil.a libanl.a libresolv.a ); \ | ||
| for n in pthread dl rt util anl resolv; do \ | ||
| [ -e "$dir/lib$n.so" ] && continue; \ | ||
| latest="$(ls -1 "$dir"/lib$n.so.* 2>/dev/null | sort -V | tail -n1 || true)"; \ | ||
| [ -n "$latest" ] && ln -s "$(basename "$latest")" "$dir/lib$n.so" || true; \ | ||
| done; \ | ||
| done; \ | ||
| find "${RPI_SYSROOT}" -type l | while IFS= read -r l; do \ | ||
| t="$(readlink "$l")"; \ | ||
| case "$t" in \ | ||
| /*) ln -sfn "$(realpath -m --relative-to="$(dirname "$l")" "${RPI_SYSROOT}${t}")" "$l" ;; \ | ||
| esac; \ | ||
| done; \ | ||
| echo "sysroot prepared" | ||
|
|
||
| # Stage the Raspbian v6 startfiles + libgcc into a clean -B dir. Debian's gcc is | ||
| # used only as the link driver; -B forces its v6 crt1.o/crti.o/crtn.o/libgcc | ||
| # instead of the ARMv7 ones it would otherwise pull in. | ||
| RUN GCCV="$(ls ${RPI_SYSROOT}/usr/lib/gcc/arm-linux-gnueabihf | grep -E '^[0-9]+$' | sort -n | tail -1)" \ | ||
| && mkdir -p ${V6GCC} \ | ||
| && cp ${RPI_SYSROOT}/usr/lib/gcc/arm-linux-gnueabihf/${GCCV}/crt*.o ${V6GCC}/ \ | ||
| && cp ${RPI_SYSROOT}/usr/lib/gcc/arm-linux-gnueabihf/${GCCV}/libgcc.a ${V6GCC}/ \ | ||
| && cp ${RPI_SYSROOT}/usr/lib/arm-linux-gnueabihf/crt1.o \ | ||
| ${RPI_SYSROOT}/usr/lib/arm-linux-gnueabihf/crti.o \ | ||
| ${RPI_SYSROOT}/usr/lib/arm-linux-gnueabihf/crtn.o \ | ||
| ${RPI_SYSROOT}/usr/lib/arm-linux-gnueabihf/Scrt1.o ${V6GCC}/ \ | ||
| && echo "v6 startfiles staged in ${V6GCC}" |
There was a problem hiding this comment.
This whole block is completely incomprehensible to me, and the documentation doesn't seem to match up with the code. What is a clean -B dir? I don't see --as-needed either? Why not write out the exact paths that need to be linked instead of this detection script?
There was a problem hiding this comment.
I've tried to boil it down now, removing any excessive build arguments and what not, but retaining a few checks in case the FROM image changes in versions. If you still find it severely unreadable, I'll probably just end up maintain my own fork.
| cargo build --jobs "$(nproc)" --profile release --target "$BUILD_TARGET" --no-default-features --features "alsa-backend pulseaudio-backend with-avahi rustls-tls-native-roots" | ||
| # TLS/feature set is overridable so the ARMv6 build can swap rustls (which | ||
| # pulls in ring's ARMv7/NEON assembly) for native-tls (OpenSSL). | ||
| CARGO_FEATURES="${CARGO_FEATURES:-alsa-backend pulseaudio-backend with-avahi rustls-tls-native-roots}" |
There was a problem hiding this comment.
One possible way to resolve the TLS discrepancies might be to use native-tls for all architectures, if possible. That would be another PR though.
This pull request adds official support for building and distributing Raspotify for ARMv6 devices (Raspberry Pi 1 and Pi Zero v1.x), which had been previously dropped. It introduces a new ARMv6 build pipeline using a dedicated Docker builder and sysroot, ensures compatibility by switching to OpenSSL for TLS, and updates documentation and install scripts to reflect the new support and limitations.
ARMv6 Build and Distribution Support:
Dockerfile.armv6and correspondingMakefiletargets and commands to build Raspotify for ARMv6 using a Raspberry Pi OS (Raspbian) sysroot and ARMv6-specific startfiles, ensuring binaries run on original Pi hardware. [1] [2] [3] [4] [5]build_armv6()function inbuild.shto configure the build for ARMv6, statically link OpenSSL, and set appropriate sysroot and linker flags. [1] [2]TLS Backend and Build Configuration:
native-tls) for ARMv6 to avoid incompatible ARMv7/NEON assembly in theringcrate.armhf(as reported by Raspberry Pi OS), even though the internal label isarmv6, for compatibility. [1] [2]Documentation and Installer Updates:
ARMV6.mddocumenting the rationale, technical details, and caveats of ARMv6 support, including build instructions and limitations.install.shto detect ARMv6 devices and direct users to manually download and install the standalone.debpackage, since ARMv6 and ARMv7 packages cannot coexist in the same apt repository.These changes collectively restore and modernize ARMv6 support for Raspotify, with clear documentation and a robust build process tailored for legacy Raspberry Pi hardware.