Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions Dockerfile.armv6
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# syntax=docker/dockerfile:1
#
# ARMv6 builder image (Raspberry Pi 1 / Zero v1.x, ARM1176JZF-S + VFPv2).
# Debian's armhf is ARMv7-baseline and SIGILLs on an ARM1176, so we:
# - link against a Raspbian ARMv6 sysroot and its v6 startfiles, not Debian's
# ARMv7 crt*.o;
# - build librespot with native-tls, not rustls (ring ships ARMv7/NEON asm).
# OpenSSL links dynamically, so the .deb depends on libssl3.
#
# `make armv6` (needs Docker QEMU/binfmt for linux/arm/v6) -> standalone
# raspotify_*_armv6.deb, not served from the apt repo.

# ---- Stage 1: Raspbian ARMv6 sysroot ----------------------------------------
# Install the dev libs under emulation, then copy the rootfs out as a cross
# sysroot (below). bookworm is the oldest suite with ARMv6 Raspbian on glibc
# >= 2.34 / libssl.so.3, so it gives the widest runtime compatibility; the
# binary runs on bookworm and newer.
FROM --platform=linux/arm/v6 balenalib/rpi-raspbian:bookworm AS sysroot
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
libc6-dev \
pkg-config \
libasound2-dev \
libpulse-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*

# ---- Stage 2: build environment --------------------------------------------
FROM rust:bullseye

ENV INSIDE_DOCKER_CONTAINER=1 \
DEBIAN_FRONTEND=noninteractive \
DEBCONF_NOWARNINGS=yes \
PATH="/root/.cargo/bin/:$PATH" \
CARGO_INSTALL_ROOT="/root/.cargo" \
CARGO_TARGET_DIR="/build" \
CARGO_HOME="/build/cache" \
RPI_SYSROOT="/opt/rpi-sysroot" \
V6GCC="/opt/v6gcc"

RUN apt-get update \
&& apt-get -y upgrade \
&& apt-get install -y --no-install-recommends \
build-essential \
gcc-arm-linux-gnueabihf \
cmake \
git \
bc \
liblzma-dev \
pkg-config \
gettext-base \
&& rm -rf /var/lib/apt/lists/* \
;

# Bring in the Raspberry Pi OS ARMv6 sysroot built in stage 1.
COPY --from=sysroot / ${RPI_SYSROOT}/

# Make that rootfs a cross-linking sysroot and stage its ARMv6 startfiles into
# ${V6GCC}; see prepare-armv6-sysroot.sh for the why of each step.
COPY prepare-armv6-sysroot.sh /usr/local/bin/prepare-armv6-sysroot.sh
RUN sh /usr/local/bin/prepare-armv6-sysroot.sh

RUN mkdir -p /build /.cargo \
&& rustup target add arm-unknown-linux-gnueabihf \
&& printf '[target.arm-unknown-linux-gnueabihf]\nlinker = "arm-linux-gnueabihf-gcc"\n' > /.cargo/config.toml \
&& cargo install --jobs "$(nproc)" cargo-deb \
;

RUN git config --global --add safe.directory /mnt/raspotify \
&& git config --global --add safe.directory /mnt/raspotify/librespot
16 changes: 15 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: all builder builder_riscv64 armhf arm64 amd64 riscv64 clean distclean
.PHONY: all builder builder_riscv64 builder_armv6 armhf armv6 arm64 amd64 riscv64 clean distclean
.DEFAULT_GOAL := all

RASPOTIFY_AUTHOR?=Kim Tore Jensen <kimtjen@gmail.com>
Expand All @@ -9,6 +9,9 @@ builder:
builder_riscv64:
docker build --pull -t raspotify_riscv64 -f Dockerfile.riscv64 .

builder_armv6:
docker build --pull -t raspotify_armv6 -f Dockerfile.armv6 .

armhf: builder
docker run \
--rm \
Expand All @@ -19,6 +22,16 @@ armhf: builder
--env ARCHITECTURE="armhf" \
raspotify /mnt/raspotify/build.sh

armv6: builder_armv6
docker run \
--rm \
--volume "$(CURDIR):/mnt/raspotify" \
--env PERMFIX_UID="$$(id -u)" \
--env PERMFIX_GID="$$(id -g)" \
--env RASPOTIFY_AUTHOR="$(RASPOTIFY_AUTHOR)" \
--env ARCHITECTURE="armv6" \
raspotify_armv6 /mnt/raspotify/build.sh

arm64: builder
docker run \
--rm \
Expand Down Expand Up @@ -57,3 +70,4 @@ clean:
distclean: clean
docker rmi -f raspotify || true
docker rmi -f raspotify_riscv64 || true
docker rmi -f raspotify_armv6 || true
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ Or you can just download the latest .deb package and install it manually from he
* [`raspotify-latest_amd64.deb`](https://dtcooper.github.io/raspotify/raspotify-latest_amd64.deb)
* [`raspotify-latest_riscv64.deb`](https://dtcooper.github.io/raspotify/raspotify-latest_riscv64.deb)

### [Raspotify does NOT support ARMv6 Pi's (Pi v1 and Pi Zero v1.x)](https://github.com/dtcooper/raspotify/wiki/Raspotify-on-Pi-v1's-and-Pi-Zero-v1.x)
### Pi v1 and Pi Zero v1.x (ARMv6)

ARMv6 is supported on Raspberry Pi OS **bookworm and newer** via a standalone `.deb`. Raspberry Pi OS reports ARMv6 as `armhf` (same as the ARMv7 build), so apt can't tell them apart — hence a manual download rather than the apt repo. Grab `raspotify_*_armv6.deb` from the [releases page](https://github.com/dtcooper/raspotify/releases) and install it:

```sh
sudo dpkg -i raspotify_*_armv6.deb
sudo apt-get -f install # pull in any missing runtime deps
```

## Configuration

Expand Down
53 changes: 50 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@ packages() {
DEB_PKG_NAME="raspotify_${DEB_PKG_VER}_${ARCHITECTURE}.deb"
echo "Prepare to build ${DEB_PKG_NAME}"

echo "Build Librespot binary..."
cargo build --jobs "$(nproc)" --profile release --target "$BUILD_TARGET" --no-default-features --features "alsa-backend pulseaudio-backend with-avahi rustls-tls-native-roots"
# Overridable so the ARMv6 build can swap rustls (ring -> ARMv7/NEON asm)
# 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.


echo "Build Librespot binary (features: $CARGO_FEATURES)..."
cargo build --jobs "$(nproc)" --profile release --target "$BUILD_TARGET" --no-default-features --features "$CARGO_FEATURES"

echo "Copy Librespot binary to package root..."
cd /mnt/raspotify
Expand All @@ -74,6 +78,10 @@ packages() {
INSTALLED_SIZE="$((($(du -bs raspotify --exclude=raspotify/DEBIAN/control | cut -f 1) + 2048) / 1024))"

echo "Generate Debian control..."
# The package arch can differ from our internal $ARCHITECTURE label: the
# ARMv6 build is labelled "armv6" for distinct artifact names, but Pi OS
# reports it as "armhf", so that is the package arch.
export DEB_ARCH="${DEB_ARCH:-$ARCHITECTURE}"
export DEB_PKG_VER
export INSTALLED_SIZE
envsubst <control.debian.tmpl >raspotify/DEBIAN/control
Expand Down Expand Up @@ -130,6 +138,40 @@ build_armhf() {
packages
}

# Raspberry Pi 1 / Pi Zero v1.x (ARM1176JZF-S, ARMv6 + VFPv2).
# Requires the ARMv6 builder image (Dockerfile.armv6), which provides a
# Raspberry Pi OS sysroot in $RPI_SYSROOT and v6 startfiles in $V6GCC.
build_armv6() {
ARCHITECTURE="armv6"
BUILD_TARGET="arm-unknown-linux-gnueabihf"
# Raspberry Pi OS reports "armhf" even on ARMv6, so that is the package arch.
DEB_ARCH="armhf"
# native-tls (OpenSSL) instead of rustls -> no ring -> no ARMv7/NEON asm.
CARGO_FEATURES="alsa-backend pulseaudio-backend with-avahi native-tls"

# Link against the Raspbian ARMv6 sysroot + its v6 startfiles. OpenSSL is
# dynamic, so OS apt upgrades (not a Raspotify rebuild) ship its CVE fixes.
export OPENSSL_NO_VENDOR=1
export OPENSSL_LIB_DIR="$RPI_SYSROOT/usr/lib/arm-linux-gnueabihf"
export OPENSSL_INCLUDE_DIR="$RPI_SYSROOT/usr/include"

# bookworm links the libssl3 package; trixie's 64-bit time_t transition
# renamed it libssl3 -> libssl3t64, so depend on either (cf. libasound2t64).
export DEB_EXTRA_DEPENDS=", libssl3t64 (>= 3.0.0) | libssl3 (>= 3.0.0)"
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_SYSROOT_DIR="$RPI_SYSROOT"
export PKG_CONFIG_PATH="$RPI_SYSROOT/usr/lib/arm-linux-gnueabihf/pkgconfig:$RPI_SYSROOT/usr/share/pkgconfig"
export CFLAGS_arm_unknown_linux_gnueabihf="-march=armv6 -mfpu=vfp -mfloat-abi=hard --sysroot=$RPI_SYSROOT"
export CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="\
-Clink-arg=--sysroot=$RPI_SYSROOT \
-Clink-arg=-B$V6GCC \
-Clink-arg=-L$RPI_SYSROOT/lib/arm-linux-gnueabihf \
-Clink-arg=-L$RPI_SYSROOT/usr/lib/arm-linux-gnueabihf \
-Clink-arg=-Wl,-rpath-link,$RPI_SYSROOT/lib/arm-linux-gnueabihf"

packages
}

build_arm64() {
ARCHITECTURE="arm64"
BUILD_TARGET="aarch64-unknown-linux-gnu"
Expand Down Expand Up @@ -176,6 +218,9 @@ case $ARCHITECTURE in
"armhf")
build_armhf
;;
"armv6")
build_armv6
;;
"arm64")
build_arm64
;;
Expand All @@ -191,7 +236,9 @@ case $ARCHITECTURE in
esac

# Fix broken permissions resulting from running the Docker container as root.
[ $(id -u) -eq 0 ] && chown -R "$PERMFIX_UID:$PERMFIX_GID" /mnt/raspotify
# Best-effort: some bind-mount backends (e.g. Docker Desktop on macOS) deny
# chown on a few VCS files; that must not fail the build after the .deb is made.
[ $(id -u) -eq 0 ] && { chown -R "$PERMFIX_UID:$PERMFIX_GID" /mnt/raspotify || true; }

BUILD_TIME=$(duration_since "$START_BUILDS")

Expand Down
4 changes: 2 additions & 2 deletions control.debian.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ Version: ${DEB_PKG_VER}
Section: base
Priority: optional
Installed-Size: ${INSTALLED_SIZE}
Architecture: ${ARCHITECTURE}
Depends: libc6 (>= 2.31), coreutils (>= 8.32), libasound2t64 (>= 1.2.4) | libasound2 (>= 1.2.4), avahi-daemon (>= 0.8), alsa-utils (>= 1.2.4), libpulse0 (>= 14.2), systemd (>= 247.3), init-system-helpers (>= 1.60)
Architecture: ${DEB_ARCH}
Depends: libc6 (>= 2.31), coreutils (>= 8.32), libasound2t64 (>= 1.2.4) | libasound2 (>= 1.2.4), avahi-daemon (>= 0.8), alsa-utils (>= 1.2.4), libpulse0 (>= 14.2), systemd (>= 247.3), init-system-helpers (>= 1.60)${DEB_EXTRA_DEPENDS}
Maintainer: ${RASPOTIFY_AUTHOR}
Homepage: https://github.com/dtcooper/raspotify
Vcs-Browser: https://github.com/dtcooper/raspotify
Expand Down
18 changes: 13 additions & 5 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@ if ! which apt-get >/dev/null; then
exit 1
fi

# Raspberry Pi OS reports ARMv6 (Pi v1 / Zero v1.x) as "armhf", same as the
# ARMv7 build, so apt can't tell them apart -- ARMv6 ships as a standalone .deb,
# not via this apt installer.
if uname -a | grep -F -iq -e armv6; then
echo "\nARMv6 detected (Pi v1 / Pi Zero v1.x):\n"
echo "Raspotify is available for ARMv6 as a standalone .deb (not via this apt repo)."
echo "Download raspotify_*_armv6.deb from the releases page and install it with:"
echo "\n sudo dpkg -i raspotify_*_armv6.deb"
echo " sudo apt-get -f install # to pull in any missing dependencies\n"
echo " https://github.com/dtcooper/raspotify/releases\n"
exit 1
fi

if uname -a | grep -F -ivq -e armv7 -e aarch64 -e x86_64 -e riscv64; then
echo "\nUnspported architecture:\n"
echo "$ERROR_MESG"
echo "\nSupport for ARMv6 (Pi v1 and Pi Zero v1.x) has been dropped."
echo "0.31.8.1 was the last version to be built with ARMv6 support."
echo "\nhttps://github.com/dtcooper/raspotify/releases/tag/0.31.8.1\n"
echo "You can install and run that version on an ARMv6 device,"
echo "but you will never get updates and doing so is completely unsupported."
exit 1
fi

Expand Down
66 changes: 66 additions & 0 deletions prepare-armv6-sysroot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/sh
# vi: set noexpandtab sw=4 ts=4 sts=4:
#
# Turn the copied-in Raspberry Pi OS (Raspbian) ARMv6 rootfs into a sysroot the
# Debian cross gcc can link against, and stage its ARMv6 startup files for that
# link step. Run once when building the ARMv6 builder image (Dockerfile.armv6).
#
# Environment inputs:
# RPI_SYSROOT the Raspbian rootfs, fixed up in place (required)
# V6GCC a dedicated dir to collect the ARMv6 startup files in (required)
#
# gcc versions and library sonames are detected rather than hard-coded: they
# change if the Raspbian base image (Dockerfile.armv6) is bumped to a newer
# suite, so detecting keeps that a one-line change instead of breaking here.

set -eu
: "${RPI_SYSROOT:?RPI_SYSROOT must be set}"
: "${V6GCC:?V6GCC must be set}"

# The arm-linux-gnueabihf library dirs. Debian splits glibc across /lib and
# /usr/lib, so both may exist; fix up each in place.
for d in usr/lib/arm-linux-gnueabihf lib/arm-linux-gnueabihf; do
dir="$RPI_SYSROOT/$d"
[ -d "$dir" ] || continue

# Drop the static glibc archives: they reference loader internals that
# don't cross-link, so removing them forces ld onto the shared libs.
for name in pthread dl rt util anl resolv m c; do
rm -f "$dir/lib$name.a"
done

# glibc >= 2.34 folded pthread/dl/rt/util/anl/resolv into libc, leaving
# only empty stub .so.N files and dropping the unversioned lib<name>.so
# that "-lpthread", "-ldl", etc. need. Recreate each one pointing at its
# stub (ln -sfn also repairs a leftover dangling symlink).
for name in pthread dl rt util anl resolv; do
[ -e "$dir/lib$name.so" ] && continue
stub=$(ls -1 "$dir/lib$name.so."* 2>/dev/null | sort -V | tail -n1 || true)
[ -n "$stub" ] && ln -sfn "$(basename "$stub")" "$dir/lib$name.so"
done
done

# ld follows absolute symlinks against the builder's real "/", where the target
# is the host's ARMv7 lib or nothing ("cannot find -lm"). Re-point every
# absolute symlink (-lname '/*') at the same file inside the sysroot, relative.
find "$RPI_SYSROOT" -type l -lname '/*' | while IFS= read -r link; do
target=$(readlink "$link")
ln -sfn "$(realpath -m --relative-to="$(dirname "$link")" "$RPI_SYSROOT$target")" "$link"
done

# Debian's arm-linux-gnueabihf-gcc is only the link driver; by default it links
# the cross toolchain's ARMv7 startup files, which SIGILL on an ARM1176. Collect
# the sysroot's ARMv6 ones into V6GCC -- nothing else lives there -- so build.sh
# can pass "-B$V6GCC" and have gcc prefer them:
# - gcc's crt*.o (crtbegin*/crtend*) and libgcc.a, from its versioned dir
# - glibc's crt1.o/crti.o/crtn.o/Scrt1.o, from the lib dir
gcc_dir="$RPI_SYSROOT/usr/lib/gcc/arm-linux-gnueabihf"
gcc_ver=$(ls "$gcc_dir" | grep -E '^[0-9]+$' | sort -n | tail -1) # newest gcc major
[ -d "$gcc_dir/$gcc_ver" ] || { echo "no gcc found in $gcc_dir" >&2; exit 1; }
lib_dir="$RPI_SYSROOT/usr/lib/arm-linux-gnueabihf"

mkdir -p "$V6GCC"
cp "$gcc_dir/$gcc_ver/"crt*.o "$gcc_dir/$gcc_ver/libgcc.a" "$V6GCC/"
cp "$lib_dir/crt1.o" "$lib_dir/crti.o" "$lib_dir/crtn.o" "$lib_dir/Scrt1.o" "$V6GCC/"

echo "ARMv6 sysroot prepared; v6 startfiles staged in $V6GCC"