Skip to content

Armv6 support#761

Open
jkiddo wants to merge 8 commits into
dtcooper:masterfrom
jkiddo:armv6-support
Open

Armv6 support#761
jkiddo wants to merge 8 commits into
dtcooper:masterfrom
jkiddo:armv6-support

Conversation

@jkiddo

@jkiddo jkiddo commented Jun 14, 2026

Copy link
Copy Markdown

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:

  • Added a new Dockerfile.armv6 and corresponding Makefile targets 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]
  • Implemented a dedicated build_armv6() function in build.sh to configure the build for ARMv6, statically link OpenSSL, and set appropriate sysroot and linker flags. [1] [2]

TLS Backend and Build Configuration:

  • Modified the build system to allow overriding TLS backend features, defaulting to OpenSSL (native-tls) for ARMv6 to avoid incompatible ARMv7/NEON assembly in the ring crate.
  • Ensured that the Debian package architecture for ARMv6 is set to armhf (as reported by Raspberry Pi OS), even though the internal label is armv6, for compatibility. [1] [2]

Documentation and Installer Updates:

  • Added a comprehensive ARMV6.md documenting the rationale, technical details, and caveats of ARMv6 support, including build instructions and limitations.
  • Updated install.sh to detect ARMv6 devices and direct users to manually download and install the standalone .deb package, 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.

jkiddo and others added 2 commits June 14, 2026 17:48
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>
@jkiddo

jkiddo commented Jun 14, 2026

Copy link
Copy Markdown
Author

Its tested on a Pi Zero v1.3 as fully functional with these changes.

@kimtore

kimtore commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks for contributing. Quite a feature there. Now there are a few issues with this approach that makes me skeptical of merging:

  • How will a statically compiled OpenSSL hold against security issues? Does it require a new build every time an OpenSSL bug is found?
  • I really don't like the out-of-band installation without using the Raspotify repositories.
  • Dockerfile is quite messy and contains a bunch of hacks.

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>
@jkiddo

jkiddo commented Jun 16, 2026

Copy link
Copy Markdown
Author

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

Comment thread Dockerfile.armv6 Outdated
Comment on lines 63 to 103
@@ -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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have a look at the new structure. I've cleaned it up.

Comment thread README.md Outdated
### 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:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this can't be worked around somehow?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread build.sh Outdated
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very happy about this kind of thing either, it adds more complexity.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 kimtore left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tl;dr code readability must be dramatically improved. Claude's documentation is garbage and must be replaced entirely by something readable.

Comment thread Dockerfile.armv6 Outdated
Comment on lines +63 to +101
# 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}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread build.sh
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}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@jkiddo jkiddo requested a review from kimtore June 28, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants