Files
jellytau/Dockerfile.builder
T
dtourolle 4daf172834 feat(windows): mpv plays audio on Windows, with libmpv shipped in the installer
libmpv on Windows (DR-237):
- The builder image carries zhongfly's LGPL libmpv-2.dll, pinned by
  asset name and sha256, plus an MSVC mpv.lib generated from the DLL's
  own mpv_* exports (the archive ships only a MinGW .dll.a). LGPL, not
  the GPL builds: no x264/x265, mpv -Dgpl=false; FFmpeg is version3, so
  LGPL-3.0. THIRD_PARTY_NOTICES.md records it.
- build-windows-cross.sh stages both files into src-tauri/windows-libs/;
  build.rs links mpv.lib from there and tauri.windows.conf.json bundles
  the DLL beside jellytau.exe from there, with the licence texts under
  licenses/. One directory, so the DLL shipped is the one linked.
- Workflows move to builder image 2026.09.1.

Windows audio:
- MpvBackend replaces WebviewAudioBackend on Windows (ao=wasapi), so
  volume, EQ, normalization and gapless work there as on Linux. Local
  files are passed to mpv as native paths, not file:// URLs.

Fixes found on the way:
- player_play_item decided "does the backend render video" with
  cfg!(not(linux)), true on Windows, while get_player_status sent
  Windows video to the <video> element. With mpv as the backend that
  would decode every film's soundtrack twice. All three callers now
  ask video_renders_natively() (UT-273).
- confine_queued_path rebuilt paths with PathBuf::push, so on Windows
  a queued `downloads/x` was stored as `downloads\x`, no longer the
  spelling the app built. It now keeps the caller's separator (UT-205,
  which only ever ran on Linux, failed under Windows).

Verified: jellytau.exe imports libmpv-2.dll; the full unit suite
cross-compiled for Windows passes under wine against the shipped DLL
(943/943, including the mpv injection and TLS tests); the NSIS
installer contains the DLL and licence texts. Not yet run on real
Windows hardware.
2026-09-24 22:25:31 -04:00

228 lines
10 KiB
Docker

# JellyTau Builder Image
# Pre-built image with all dependencies for building, testing, and packaging:
# - Android APK (SDK/NDK), Linux desktop (deb/rpm),
# - Windows cross via the official Tauri path: MSVC target + cargo-xwin + NSIS
# Arch packages build in a separate archlinux image (Dockerfile.arch) since
# makepkg is Arch-specific.
# Push to your registry: docker build -f Dockerfile.builder -t gitea.tourolle.paris/dtourolle/jellytau-builder:latest .
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
ANDROID_HOME=/opt/android-sdk \
NDK_VERSION=27.0.11902837 \
SDK_VERSION=36 \
BUILD_TOOLS_VERSION=35.0.0 \
RUST_BACKTRACE=1 \
PATH="/root/.bun/bin:/root/.cargo/bin:$PATH" \
CARGO_HOME=/root/.cargo
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
curl \
wget \
git \
ca-certificates \
unzip \
jq \
openjdk-17-jdk-headless \
pkg-config \
libssl-dev \
libclang-dev \
llvm-dev \
# Tauri Linux desktop dependencies (needed for `cargo test` on the host target)
libglib2.0-dev \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
libjavascriptcoregtk-4.1-dev \
libsoup-3.0-dev \
librsvg2-dev \
libayatana-appindicator3-dev \
# mpv player library (linked via libmpv-sys)
libmpv-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js 20.x from NodeSource
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
# Install Bun
RUN curl -fsSL https://bun.sh/install | bash && \
ln -s /root/.bun/bin/bun /usr/local/bin/bun
# Install Rust using rustup, pinned to an exact release.
#
# 🔴 RUST_VERSION must equal `channel` in src-tauri/rust-toolchain.toml.
#
# The two are a pair. rust-toolchain.toml is what makes a developer's `cargo
# clippy` agree with CI's; this line is what makes the image already contain that
# toolchain. If they drift, rustup silently downloads the pinned version the
# first time cargo runs inside a job — a toolchain install at job time, which
# CLAUDE.md's "🔴 CI installs no system tools" rule forbids (and which costs
# ~1min plus a network dependency on every build).
#
# 🔴 Changing this line does NOT change CI on its own: the image must be
# rebuilt and pushed (`scripts/build-builder-image.sh`) before the new pin is
# authoritative. Bump rust-toolchain.toml and this line together, rebuild, push,
# then merge.
#
# Was: `sh -s -- -y` (latest stable, whatever it happened to be on rebuild day).
ENV RUST_VERSION=1.97.1
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain "$RUST_VERSION" && \
. $HOME/.cargo/env && \
rustup default "$RUST_VERSION" && \
rustup target add aarch64-linux-android && \
rustup target add armv7-linux-androideabi && \
rustup target add x86_64-linux-android && \
rustup component add rustfmt clippy && \
rustc --version && \
cargo clippy --version
# Setup Android SDK
RUN mkdir -p $ANDROID_HOME && \
mkdir -p /root/.android && \
echo '### User Sources for `android` cmd line tool ###' > /root/.android/repositories.cfg && \
echo 'count=0' >> /root/.android/repositories.cfg
# Download and setup Android Command Line Tools
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip && \
unzip -q /tmp/cmdline-tools.zip -d $ANDROID_HOME && \
rm /tmp/cmdline-tools.zip && \
mkdir -p $ANDROID_HOME/cmdline-tools/latest && \
mv $ANDROID_HOME/cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest/ 2>/dev/null || true
# Accept all SDK licenses up front so Gradle can install/use components non-interactively
RUN yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_HOME --licenses > /dev/null
# Install Android SDK components (must match the compileSdk/targetSdk in the generated Gradle project)
RUN $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_HOME \
"platform-tools" \
"platforms;android-$SDK_VERSION" \
"build-tools;$BUILD_TOOLS_VERSION" \
"ndk;$NDK_VERSION" \
--channel=0 2>&1 | grep -v "Warning" || true
# Set NDK environment variable
ENV NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION
# Gradle distribution. `tauri android init` regenerates gen/android with a
# wrapper pointing at services.gradle.org, so every Android job would otherwise
# download ~130MB of Gradle at build time — slow, and a hard failure when the
# CDN hiccups ("Unexpected end of file from server"). Ship the distribution in
# the image instead; scripts/sync-android-sources.sh repoints the regenerated
# wrapper at this local copy. Keep GRADLE_VERSION in sync with the version
# Tauri's generated wrapper requests.
ENV GRADLE_VERSION=8.14.3 \
GRADLE_HOME=/opt/gradle/gradle-8.14.3
RUN mkdir -p /opt/gradle/dist && \
wget -q "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
-O "/opt/gradle/dist/gradle-${GRADLE_VERSION}-bin.zip" && \
unzip -q "/opt/gradle/dist/gradle-${GRADLE_VERSION}-bin.zip" -d /opt/gradle && \
"$GRADLE_HOME/bin/gradle" --version
ENV PATH="$GRADLE_HOME/bin:$PATH"
# ---------------------------------------------------------------------------
# Desktop packaging tools — kept in a trailing layer ON PURPOSE so that adding
# or changing a packaging tool doesn't invalidate the expensive apt/rust/Android
# layers above (a tool tweak becomes a ~1-2 min rebuild, not ~15). Covers Linux
# (deb/rpm) and Windows cross (MSVC via cargo-xwin + NSIS).
RUN apt-get update && apt-get install -y --no-install-recommends \
# Linux desktop packaging: rpmbuild for the .rpm bundle (deb needs nothing extra)
rpm \
file \
# Windows cross-compile (official Tauri path: MSVC target via cargo-xwin).
# clang provides clang-cl, the MSVC-compatible C compiler cc-rs uses to build
# C deps (bundled sqlite, ring, ...); lld = linker; llvm = llvm-lib/ar etc;
# nsis = installer generator.
clang \
lld \
llvm \
nsis \
# AppImage bundling. linuxdeploy embeds xdg-open into the AppImage and
# aborts the whole bundle if it is missing:
# failed to bundle project: xdg-open binary not found
# It is present on most desktop distros, which is why the AppImage built on
# a developer machine and failed here. desktop-file-utils and zsync are the
# other two linuxdeploy commonly wants (desktop-file-validate, and zsync for
# delta updates), added together so a missing one does not cost another
# image rebuild and another failed release build.
xdg-utils \
desktop-file-utils \
zsync \
&& rm -rf /var/lib/apt/lists/* \
# Ubuntu's clang package ships clang but NOT the clang-cl alias that cc-rs
# invokes for MSVC targets. clang-cl is the same binary in MSVC-compat mode,
# so provide it as a symlink.
&& ln -sf /usr/bin/clang /usr/local/bin/clang-cl
# Windows rust target + cargo-xwin (downloads the MSVC CRT/SDK at build time).
RUN . $HOME/.cargo/env && \
rustup target add x86_64-pc-windows-msvc && \
cargo install --locked cargo-xwin
# ---------------------------------------------------------------------------
# Supply-chain and docs tooling.
#
# cargo-deny — advisories/licences/bans/sources gate (src-tauri/deny.toml),
# run by the `security` job. It fetches the RustSec advisory
# database at run time; that is *data*, not a toolchain, so it
# does not breach the no-installs-in-CI rule.
# cargo-cyclonedx — SBOM for the Rust half of a release.
# mdbook — builds the docs site. It used to be curl'd from GitHub
# releases *inside* the job (publish-docs.yml), which was both a
# breach of that rule and a hard dependency on GitHub's CDN
# being up at publish time. Pinned to the version that job used.
ENV MDBOOK_VERSION=v0.4.40
RUN . $HOME/.cargo/env && \
cargo install --locked cargo-deny cargo-cyclonedx && \
wget -q "https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \
-O /tmp/mdbook.tar.gz && \
tar -xzf /tmp/mdbook.tar.gz -C /usr/local/bin && \
rm /tmp/mdbook.tar.gz && \
cargo deny --version && \
cargo cyclonedx --version && \
mdbook --version
# ---------------------------------------------------------------------------
# libmpv for Windows (DR-237) — the Windows build links it and ships the DLL.
#
# zhongfly/mpv-winbuild's **LGPL** dev build: libmpv-2.dll with FFmpeg compiled
# in, built `-Dgpl=false` with no x264/x265 and no --enable-gpl (its
# compile-lgpl-libmpv.patch). FFmpeg keeps --enable-version3, so the DLL is
# LGPL-3.0: the installer ships the licence texts and keeps the DLL a separate,
# replaceable file — THIRD_PARTY_NOTICES.md. Not the default (GPL) asset from
# the same release, and not shinchiro's, which is GPL-3.0 only.
#
# Pinned by asset name *and* sha256: GitHub prunes old releases from that repo,
# so a rebuild after pruning fails loudly here rather than quietly taking a
# newer build. To bump, change all three values.
#
# The archive ships a MinGW import library (libmpv.dll.a); the MSVC target needs
# `mpv.lib`, generated from the DLL's own mpv_* exports so it cannot name a
# symbol the DLL lacks. Output: $LIBMPV_WIN_DIR/{libmpv-2.dll,mpv.lib,include}.
ENV LIBMPV_WIN_RELEASE=2026-09-24-2a4eb8067c \
LIBMPV_WIN_ASSET=mpv-dev-lgpl-x86_64-20260924-git-2a4eb8067c.7z \
LIBMPV_WIN_SHA256=f89c6195e13bfce3e8c0cc5d7f5d889ebdcf12184a41c8288360f5acd1e7306c \
LIBMPV_WIN_DIR=/opt/libmpv-win64
RUN apt-get update && apt-get install -y --no-install-recommends libarchive-tools \
&& rm -rf /var/lib/apt/lists/* \
&& wget -q "https://github.com/zhongfly/mpv-winbuild/releases/download/${LIBMPV_WIN_RELEASE}/${LIBMPV_WIN_ASSET}" \
-O /tmp/mpv-dev.7z \
&& echo "${LIBMPV_WIN_SHA256} /tmp/mpv-dev.7z" | sha256sum -c - \
&& mkdir -p "$LIBMPV_WIN_DIR" \
&& bsdtar -xf /tmp/mpv-dev.7z -C "$LIBMPV_WIN_DIR" libmpv-2.dll include \
&& rm /tmp/mpv-dev.7z \
&& { echo "LIBRARY libmpv-2.dll"; echo "EXPORTS"; \
llvm-readobj --coff-exports "$LIBMPV_WIN_DIR/libmpv-2.dll" \
| awk '/Name: mpv_/ { print " " $2 }'; } > "$LIBMPV_WIN_DIR/mpv.def" \
&& llvm-lib /def:"$LIBMPV_WIN_DIR/mpv.def" /out:"$LIBMPV_WIN_DIR/mpv.lib" /machine:x64 \
&& test "$(grep -c '^ mpv_' "$LIBMPV_WIN_DIR/mpv.def")" -ge 50 \
&& ls -la "$LIBMPV_WIN_DIR"
WORKDIR /app
ENTRYPOINT ["/bin/bash"]