Makefile.toml
Rama main · réplica de lectura del repositorio original.
[env]
CARGO_TARGET_DIR = "target"
CROSS_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
# TARGET_HOST = "192.168.5.3"
# TARGET = "arm-unknown-linux-gnueabihf"
TARGET_HOST = "192.168.5.4"
TARGET = "aarch64-unknown-linux-gnu"
# Version single source of truth is [workspace.package] in Cargo.toml
# TARGET = "x86_64-unknown-linux-gnu"
RELEASE_VERSION = { script = ["grep -m1 '^version' Cargo.toml | cut -d'\"' -f2"] }
RELEASE_BRANCH = "main_public"
[config]
default_to_workspace = false
# ═══════════════════════════════════════════
# RELEASE BUILD TASKS (used by CI workflows)
# ═══════════════════════════════════════════
# ── FE (Web UI) ───────────────────────────
[tasks.npm_install]
description = "Install npm dependencies for web-ui"
cwd = "web-ui"
command = "npm"
args = ["install"]
[tasks.build_css]
dependencies = ["npm_install"]
description = "Build Tailwind CSS + DaisyUI (minified)"
cwd = "web-ui"
command = "npx"
args = ["tailwindcss", "-i", "input.css", "-o", "public/tw.css", "--minify"]
[tasks.build_ui_release]
description = "Build CSS + release WASM with Dioxus and copy to dist/"
dependencies = ["build_css"]
run_task = "build_ui_wasm_release"
[tasks.build_ui_wasm_release]
description = "Release WASM with Dioxus from the committed public/tw.css (no npm), copied to dist/"
cwd = "web-ui"
script = [
"# dx never removes old hashed assets, and everything in dist/ ends up embedded in the binaries",
"rm -rf target/dx/rsplayer_web_ui/release",
"dx build --platform web --release --debug-symbols false --package rsplayer_web_ui",
"# Copy output outside target/ so cross Docker containers can see it",
"rm -rf ../dist/web-ui",
"mkdir -p ../dist/web-ui",
"cp -r target/dx/rsplayer_web_ui/release/web/public/ ../dist/web-ui/",
]
# ── BE (Backend) ──────────────────────────
[tasks.build_desktop_release]
description = "Build web UI + desktop release bundle"
dependencies = ["build_ui_release"]
run_task = "bundle_desktop_release"
[tasks.bundle_desktop_release]
description = "Build desktop release bundle (deb+rpm+arch tgz on Linux via Tauri). Native build: output follows the host arch (x86_64 or aarch64)."
script = [
"set -e",
"# Package dir and arch suffix follow the host (not ${TARGET}, which defaults to aarch64 for Pi deploys)",
"HOST_ARCH=$(uname -m)",
"case ${HOST_ARCH} in aarch64) ARCH_SUFFIX=arm64 ;; *) ARCH_SUFFIX=amd64 ;; esac",
"cd crates/desktop",
"# Copy loading.html to frontend dist (Tauri expects it alongside the WASM app)",
"cp loading.html ../../dist/web-ui/loading.html",
"# Build the pkexec mount helper first — tauri.conf.json bundles it into deb/rpm",
"cargo build --release --package rsplayer-mount-helper",
"cargo tauri build --bundles deb,rpm --ci",
"PKG_DIR=$(pwd)/../../target/cross/${HOST_ARCH}-unknown-linux-gnu/pkg",
"ARCH_DIR=$(pwd)/../../target/cross/${HOST_ARCH}-unknown-linux-gnu/arch-desktop",
"mkdir -p ${PKG_DIR}",
"mv target/release/bundle/deb/rsplayer-desktop_*.deb ${PKG_DIR}/",
"mv target/release/bundle/rpm/rsplayer-desktop-*.rpm ${PKG_DIR}/",
"# Build Arch Linux tgz package",
"mkdir -p ${ARCH_DIR}/rootfs/usr/bin",
"mkdir -p ${ARCH_DIR}/rootfs/usr/share/applications",
"mkdir -p ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/32x32/apps",
"mkdir -p ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/128x128/apps",
"mkdir -p ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/256x256/apps",
"mkdir -p ${ARCH_DIR}/rootfs/usr/libexec",
"mkdir -p ${ARCH_DIR}/rootfs/usr/share/polkit-1/actions",
"cp target/release/rsplayer-desktop ${ARCH_DIR}/rootfs/usr/bin/rsplayer-desktop",
"cp target/release/rsplayer-mount-helper ${ARCH_DIR}/rootfs/usr/libexec/rsplayer-mount-helper",
"cp ../../PKGS/desktop/io.github.ljufa.rsplayer.policy ${ARCH_DIR}/rootfs/usr/share/polkit-1/actions/io.github.ljufa.rsplayer.policy",
"cp ../../PKGS/arch/desktop/rsplayer-desktop.desktop ${ARCH_DIR}/rootfs/usr/share/applications/rsplayer-desktop.desktop",
"cp ../../crates/desktop/icons/32x32.png ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/32x32/apps/rsplayer-desktop.png",
"cp ../../crates/desktop/icons/128x128.png ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/128x128/apps/rsplayer-desktop.png",
"cp ../../crates/desktop/icons/128x128@2x.png ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/256x256/apps/rsplayer-desktop.png",
"RELEASE_VERSION=$(grep -m1 '^version' ../../Cargo.toml | cut -d'\"' -f2)",
"(cd ${ARCH_DIR}/rootfs && tar -czf ${PKG_DIR}/rsplayer-desktop_${RELEASE_VERSION}_${ARCH_SUFFIX}.tgz .)",
"rm -rf ${ARCH_DIR}/rootfs",
"echo \"Arch desktop package: ${PKG_DIR}/rsplayer-desktop_${RELEASE_VERSION}_${ARCH_SUFFIX}.tgz\"",
]
[tasks.bundle_desktop_release_aarch64]
description = "Cross-build the desktop app for aarch64 (RPi 4/5) with cross (same rsplayer-cross-aarch64 image as the server) and bundle deb+rpm+Arch tgz. Needs dist/web-ui (build_ui_release)."
script = [
"set -e",
"T=aarch64-unknown-linux-gnu",
"case \"${CARGO_TARGET_DIR}\" in /*) OUT=\"${CARGO_TARGET_DIR}\" ;; *) OUT=\"$(pwd)/${CARGO_TARGET_DIR}\" ;; esac",
"REL=${OUT}/${T}/release",
"# Tauri embeds the frontend dist (incl. loading.html) at compile time",
"cp crates/desktop/loading.html dist/web-ui/loading.html",
"cross build --target ${T} --release --package rsplayer-desktop --package rsplayer-mount-helper --features rsplayer-desktop/custom-protocol",
"# Package the cross-built binaries without recompiling; tauri.conf.json points the bundled mount helper at the host build, so override it",
"OVERRIDE='{\"bundle\":{\"linux\":{\"deb\":{\"files\":{\"/usr/libexec/rsplayer-mount-helper\":\"'${REL}'/rsplayer-mount-helper\"}},\"rpm\":{\"files\":{\"/usr/libexec/rsplayer-mount-helper\":\"'${REL}'/rsplayer-mount-helper\"}}}}}'",
"(cd crates/desktop && CARGO_TARGET_DIR=${OUT} cargo tauri bundle --target ${T} --bundles deb,rpm --ci -c \"${OVERRIDE}\")",
"PKG_DIR=${OUT}/${T}/pkg",
"ARCH_DIR=${OUT}/${T}/arch-desktop",
"mkdir -p ${PKG_DIR}",
"mv ${REL}/bundle/deb/rsplayer-desktop_*_arm64.deb ${PKG_DIR}/",
"mv ${REL}/bundle/rpm/rsplayer-desktop-*.aarch64.rpm ${PKG_DIR}/",
"# Arch Linux tgz (same layout as bundle_desktop_release)",
"mkdir -p ${ARCH_DIR}/rootfs/usr/bin ${ARCH_DIR}/rootfs/usr/libexec ${ARCH_DIR}/rootfs/usr/share/applications ${ARCH_DIR}/rootfs/usr/share/polkit-1/actions",
"mkdir -p ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/32x32/apps ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/128x128/apps ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/256x256/apps",
"cp ${REL}/rsplayer-desktop ${ARCH_DIR}/rootfs/usr/bin/rsplayer-desktop",
"cp ${REL}/rsplayer-mount-helper ${ARCH_DIR}/rootfs/usr/libexec/rsplayer-mount-helper",
"cp PKGS/desktop/io.github.ljufa.rsplayer.policy ${ARCH_DIR}/rootfs/usr/share/polkit-1/actions/io.github.ljufa.rsplayer.policy",
"cp PKGS/arch/desktop/rsplayer-desktop.desktop ${ARCH_DIR}/rootfs/usr/share/applications/rsplayer-desktop.desktop",
"cp crates/desktop/icons/32x32.png ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/32x32/apps/rsplayer-desktop.png",
"cp crates/desktop/icons/128x128.png ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/128x128/apps/rsplayer-desktop.png",
"cp crates/desktop/icons/128x128@2x.png ${ARCH_DIR}/rootfs/usr/share/icons/hicolor/256x256/apps/rsplayer-desktop.png",
"RELEASE_VERSION=$(grep -m1 '^version' Cargo.toml | cut -d'\"' -f2)",
"(cd ${ARCH_DIR}/rootfs && tar -czf ${PKG_DIR}/rsplayer-desktop_${RELEASE_VERSION}_arm64.tgz .)",
"rm -rf ${ARCH_DIR}/rootfs",
"ls -la ${PKG_DIR}/rsplayer-desktop*",
]
[tasks.bundle_desktop_release_aarch64.env]
CARGO_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
RUST_SYSROOT_LIB = { script = ["echo $(rustc --print sysroot)/lib"] }
CROSS_CONTAINER_OPTS = "-e LD_LIBRARY_PATH=${RUST_SYSROOT_LIB} -v ${RUST_SYSROOT_LIB}:${RUST_SYSROOT_LIB}"
[tasks.build_release]
script = [
"# Clean native build-scripts that may have been cached by rust-cache",
"# from a different glibc environment than the cross containers",
"cargo clean --target ${TARGET}",
"cross build --target ${TARGET} --release --package rsplayer --bin rsplayer",
]
[tasks.build_release.env]
CARGO_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
RUST_SYSROOT_LIB = { script = ["echo $(rustc --print sysroot)/lib"] }
CROSS_CONTAINER_OPTS = "-e LD_LIBRARY_PATH=${RUST_SYSROOT_LIB} -v ${RUST_SYSROOT_LIB}:${RUST_SYSROOT_LIB}"
# ── Packaging ─────────────────────────────
# Each packaging task places the final, release-ready asset (exact file name
# published on GitHub releases) into ${CARGO_TARGET_DIR}/${TARGET}/pkg/
[tasks.package_deb_release.env]
CARGO_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
[tasks.package_deb_release]
script = [
"set -e",
"case ${TARGET} in",
" arm-unknown-linux-gnueabihf) suffix=armhfv6 ;;",
" armv7-unknown-linux-gnueabihf) suffix=armhfv7 ;;",
" aarch64-unknown-linux-gnu) suffix=arm64 ;;",
" x86_64-unknown-linux-gnu) suffix=amd64 ;;",
" riscv64gc-unknown-linux-gnu) suffix=riscv64 ;;",
" *) suffix=${TARGET} ;;",
"esac",
"PKG_DIR=${CARGO_TARGET_DIR}/${TARGET}/pkg",
"mkdir -p ${PKG_DIR}",
"cargo deb --no-build -p rsplayer --target=${TARGET} -o ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_${suffix}.deb",
'echo "Deb package: ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_${suffix}.deb"',
]
[tasks.package_rpm_release.env]
CARGO_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
[tasks.package_rpm_release]
script = [
"set -e",
"case ${TARGET} in",
" arm-unknown-linux-gnueabihf) rpm_arch=armv6hl ;;",
" armv7-unknown-linux-gnueabihf) rpm_arch=armv7hl ;;",
" aarch64-unknown-linux-gnu) rpm_arch=aarch64 ;;",
" x86_64-unknown-linux-gnu) rpm_arch=x86_64 ;;",
" riscv64gc-unknown-linux-gnu) rpm_arch=riscv64 ;;",
" *) rpm_arch=${TARGET} ;;",
"esac",
"PKG_DIR=${CARGO_TARGET_DIR}/${TARGET}/pkg",
"mkdir -p ${PKG_DIR}",
"cargo generate-rpm -p crates/server --target ${TARGET} --target-dir ${CARGO_TARGET_DIR} --arch ${rpm_arch} --payload-compress gzip -o ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_${rpm_arch}.rpm",
'echo "RPM package: ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_${rpm_arch}.rpm"',
]
[tasks.package_arch_release.env]
CARGO_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
[tasks.package_arch_release]
script = [
"set -e",
"ARCH_DIR=${CARGO_TARGET_DIR}/${TARGET}/arch",
"mkdir -p ${ARCH_DIR}",
"cp ${CARGO_TARGET_DIR}/${TARGET}/release/rsplayer ${ARCH_DIR}/rsplayer",
"cp PKGS/debian/etc/systemd/system/rsplayer.service ${ARCH_DIR}/rsplayer.service",
"cp PKGS/debian/etc/polkit-1/rules.d/99-rsplayer.rules ${ARCH_DIR}/99-rsplayer.rules",
"cp PKGS/debian/opt/rsplayer/env ${ARCH_DIR}/env",
"cp PKGS/debian/opt/rsplayer/self.crt ${ARCH_DIR}/self.crt",
"cp PKGS/debian/opt/rsplayer/self.key ${ARCH_DIR}/self.key",
"case ${TARGET} in",
" arm-unknown-linux-gnueabihf) arch_arch=armhfv6 ;;",
" armv7-unknown-linux-gnueabihf) arch_arch=armhfv7 ;;",
" aarch64-unknown-linux-gnu) arch_arch=arm64 ;;",
" x86_64-unknown-linux-gnu) arch_arch=amd64 ;;",
" riscv64gc-unknown-linux-gnu) arch_arch=riscv64 ;;",
" *) arch_arch=${TARGET} ;;",
"esac",
"mkdir -p ${ARCH_DIR}/rootfs/usr/bin ${ARCH_DIR}/rootfs/etc/systemd/system ${ARCH_DIR}/rootfs/etc/polkit-1/rules.d ${ARCH_DIR}/rootfs/opt/rsplayer",
"cp ${ARCH_DIR}/rsplayer ${ARCH_DIR}/rootfs/usr/bin/rsplayer && chmod 755 ${ARCH_DIR}/rootfs/usr/bin/rsplayer",
"cp ${ARCH_DIR}/rsplayer.service ${ARCH_DIR}/rootfs/etc/systemd/system/rsplayer.service",
"cp ${ARCH_DIR}/99-rsplayer.rules ${ARCH_DIR}/rootfs/etc/polkit-1/rules.d/99-rsplayer.rules",
"cp ${ARCH_DIR}/env ${ARCH_DIR}/rootfs/opt/rsplayer/env",
"cp ${ARCH_DIR}/self.crt ${ARCH_DIR}/rootfs/opt/rsplayer/self.crt",
"cp ${ARCH_DIR}/self.key ${ARCH_DIR}/rootfs/opt/rsplayer/self.key",
"PKG_DIR=${CARGO_TARGET_DIR}/${TARGET}/pkg",
"mkdir -p ${PKG_DIR}",
"cd ${ARCH_DIR}/rootfs && tar -czf ../../pkg/rsplayer_${RELEASE_VERSION}_${arch_arch}.tgz . && cd .. && rm -rf rootfs",
'echo "Arch package: ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_${arch_arch}.tgz"',
]
[tasks.stage_binary_release.env]
CARGO_TARGET_DIR = { value = "target/cross", condition = { env_not_set = ["CI"] } }
[tasks.stage_binary_release]
description = "Copy raw release binary into pkg dir under its release asset name"
script = [
"set -e",
"case ${TARGET} in",
" arm-unknown-linux-gnueabihf) suffix=armhfv6 ;;",
" armv7-unknown-linux-gnueabihf) suffix=armhfv7 ;;",
" aarch64-unknown-linux-gnu) suffix=arm64 ;;",
" x86_64-unknown-linux-gnu) suffix=amd64 ;;",
" riscv64gc-unknown-linux-gnu) suffix=riscv64 ;;",
" aarch64-apple-darwin) suffix=darwin_arm64 ;;",
" x86_64-apple-darwin) suffix=darwin_amd64 ;;",
" *) suffix=${TARGET} ;;",
"esac",
"PKG_DIR=${CARGO_TARGET_DIR}/${TARGET}/pkg",
"mkdir -p ${PKG_DIR}",
"cp ${CARGO_TARGET_DIR}/${TARGET}/release/rsplayer ${PKG_DIR}/rsplayer_${suffix}",
]
[tasks.package_linux_release]
description = "Produce all Linux release assets (deb, rpm, arch tgz, raw binary) for ${TARGET}"
dependencies = [
"package_deb_release",
"package_rpm_release",
"package_arch_release",
"stage_binary_release",
]
[tasks.package_arch_pkgs]
description = "Build the Arch packages (rsplayer-bin, rsplayer-desktop-bin .pkg.tar.zst) from the release tgz files in ARCH_ASSETS (default: every target/cross/*/pkg), in an archlinux container. Output: target/arch-pkg/"
script = [
"set -e",
"RELEASE_VERSION=$(grep -m1 '^version' Cargo.toml | cut -d'\"' -f2)",
"ASSETS=$(pwd)/target/arch-pkg/assets",
"rm -rf target/arch-pkg && mkdir -p ${ASSETS}",
"if [ -n \"${ARCH_ASSETS}\" ]; then cp ${ARCH_ASSETS}/*.tgz ${ASSETS}/; else cp target/cross/*/pkg/*.tgz ${ASSETS}/; fi",
"docker run --rm -v $(pwd):/src:ro -v ${ASSETS}:/assets:ro -v $(pwd)/target/arch-pkg:/out archlinux:latest bash -c \"pacman -Syu --noconfirm --needed base-devel >/dev/null && bash /src/PKGS/arch/build-pkgs.sh ${RELEASE_VERSION} /assets /out && chown -R $(id -u):$(id -g) /out\"",
]
# ═══════════════════════════════════════════
# LOCAL / DEV TASKS
# ═══════════════════════════════════════════
# ── FE (Web UI) ───────────────────────────
[tasks.build_css_dev]
description = "Build Tailwind CSS + DaisyUI (minified like the release, so the committed public/tw.css only changes with its content)"
cwd = "web-ui"
command = "npx"
args = ["tailwindcss", "-i", "input.css", "-o", "public/tw.css", "--minify"]
[tasks.watch_css]
description = "Watch input.css and rebuild Tailwind on changes"
cwd = "web-ui"
command = "npx"
args = ["tailwindcss", "-i", "input.css", "-o", "public/tw.css", "--minify", "--watch"]
[tasks.dx_serve]
description = "Run dx serve for dev"
cwd = "web-ui"
command = "dx"
args = ["serve", "--platform", "web", "--package", "rsplayer_web_ui"]
[tasks.serve_dev]
description = "Run Tailwind watch + dx serve concurrently"
cwd = "web-ui"
run_task = { name = ["watch_css", "dx_serve"], parallel = true }
[tasks.build_ui_dev]
description = "Build dev WASM with Dioxus"
cwd = "web-ui"
dependencies = ["build_css_dev"]
command = "dx"
args = ["build", "--platform", "web", "--package", "rsplayer_web_ui"]
# ── Desktop ───────────────────────────────
[tasks.run_desktop_dev]
description = "Build release UI + run the desktop app locally via cargo tauri dev"
dependencies = ["build_ui_release"]
env = { "RUST_LOG" = "rsplayer=info,playback=debug,metadata=info,symphonia=info,warp=info", "RUST_BACKTRACE" = "full" }
script = [
"cp crates/desktop/loading.html dist/web-ui/loading.html",
"cd crates/desktop && cargo tauri dev",
]
# ── Android (Tauri mobile) ────────────────
# Needs ANDROID_HOME, NDK_HOME and JAVA_HOME plus the android rust targets —
# see docs/build.md "Android". The generated Gradle project lives in
# crates/desktop/gen/android and is committed (Kotlin media service, manifest).
[tasks.android_init]
description = "One-time: (re)generate crates/desktop/gen/android via cargo tauri android init"
script = ["cd crates/desktop && cargo tauri android init"]
[tasks.run_android_dev]
description = "Build release UI + run the Android app on the connected device/emulator (ANDROID_TARGET=aarch64|x86_64|armv7)"
dependencies = ["build_ui_release"]
env = { "ANDROID_TARGET" = { value = "aarch64", condition = { env_not_set = ["ANDROID_TARGET"] } } }
script = [
"cp crates/desktop/loading.html dist/web-ui/loading.html",
"(cd crates/desktop && cargo tauri android dev --target ${ANDROID_TARGET})",
]
[tasks.build_android_dev]
description = "Debug-signed universal APK for sideloading (adb install), arm64 + x86_64"
dependencies = ["build_ui_release"]
script = [
"cp crates/desktop/loading.html dist/web-ui/loading.html",
"(cd crates/desktop && cargo tauri android build --debug --apk --target aarch64 --target x86_64)",
"ls -la crates/desktop/gen/android/app/build/outputs/apk/universal/debug/",
]
[tasks.build_ui_android]
description = "Release UI built with the Android reproducible-build environment (see crates/desktop/android-build-env.sh): no npm, committed CSS and fonts, dx without downloads"
script = [". ./crates/desktop/android-build-env.sh && cargo make build_ui_wasm_release"]
[tasks.build_android_release]
description = "Build release UI + universal APK/AAB (arm64 + armv7) + one APK per ABI (arm64, armv7, x86_64) into target/android/pkg"
dependencies = ["build_ui_android", "bundle_android_release", "bundle_android_abi_release"]
[tasks.bundle_android_release]
description = "Release APK + AAB from the existing dist/web-ui (which must come from build_ui_android to be reproducible), signed when crates/desktop/gen/android/keystore.properties exists"
script = [
"set -e",
"cp crates/desktop/loading.html dist/web-ui/loading.html",
"(cd crates/desktop && . ./android-build-env.sh && cargo tauri android build --apk --aab --target aarch64 --target armv7 --ci)",
"PKG_DIR=target/android/pkg && rm -rf ${PKG_DIR} && mkdir -p ${PKG_DIR}",
"cp crates/desktop/gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_android.aab",
"cp crates/desktop/gen/android/app/build/outputs/apk/universal/release/app-universal-release*.apk ${PKG_DIR}/rsplayer_${RELEASE_VERSION}_android.apk",
"ls -la ${PKG_DIR}",
]
[tasks.bundle_android_abi_release]
description = "One APK per ABI (the inputs F-Droid's reproducible-build check compares against); run after bundle_android_release, which recreates target/android/pkg"
script = [
"set -e",
"cp crates/desktop/loading.html dist/web-ui/loading.html",
"mkdir -p target/android/pkg",
"# RSPLAYER_ABI_CODE (1 armv7, 2 arm64, 4 x86_64) is the last digit of the APK versionCode, see app/build.gradle.kts.",
"# x86_64 is for emulators and x86 Chromebooks: under ARM translation the arm64 library cannot see the environment the Kotlin host sets.",
"for spec in aarch64:2:arm64-v8a armv7:1:armeabi-v7a x86_64:4:x86_64; do",
" target=${spec%%:*}; rest=${spec#*:}; code=${rest%%:*}; abi=${rest#*:}",
" (cd crates/desktop && . ./android-build-env.sh && RSPLAYER_ABI_CODE=$code cargo tauri android build --apk --target $target --ci)",
" cp crates/desktop/gen/android/app/build/outputs/apk/universal/release/app-universal-release*.apk target/android/pkg/rsplayer_${RELEASE_VERSION}_android_${abi}.apk",
"done",
"ls -la target/android/pkg",
]
# ── Flatpak (local build & run) ───────────
# Builds the desktop app as a flatpak from the *local working tree*
# (including uncommitted changes) and installs it into the user flatpak
# installation. Releases are built the same way by the build_flatpak job in
# .github/workflows/cd.yml and published to the self-hosted repo at
# https://ljufa.github.io/rsplayer-flatpak; see PKGS/flatpak/README.md.
[tasks.flatpak_cargo_sources]
description = "Generate PKGS/flatpak/cargo-sources.json from Cargo.lock (skipped when up to date)"
script = [
"set -e",
"OUT=PKGS/flatpak/cargo-sources.json",
"if [ -f $OUT ] && [ $OUT -nt Cargo.lock ]; then echo 'cargo-sources.json is up to date'; exit 0; fi",
"# Self-contained tooling under target/flatpak: generator script + python venv",
"TOOLS=target/flatpak/tools",
"mkdir -p $TOOLS",
"if [ ! -f $TOOLS/flatpak-cargo-generator.py ]; then curl -sfLo $TOOLS/flatpak-cargo-generator.py https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py; fi",
"if [ ! -x $TOOLS/venv/bin/python3 ]; then python3 -m venv $TOOLS/venv && $TOOLS/venv/bin/pip -q install aiohttp toml tomlkit; fi",
"$TOOLS/venv/bin/python3 $TOOLS/flatpak-cargo-generator.py Cargo.lock -o $OUT",
]
[tasks.build_flatpak]
description = "Build the desktop app flatpak from the working tree and install it (user installation)"
dependencies = ["flatpak_cargo_sources"]
script = [
"set -e",
"# Stage tracked + untracked (non-ignored) files plus the gitignored web UI dist",
"bash PKGS/flatpak/stage-sources.sh target/flatpak/src",
"cp PKGS/flatpak/io.github.ljufa.rsplayer.yml PKGS/flatpak/cargo-sources.json target/flatpak/",
"# Use native flatpak-builder when present, else the org.flatpak.Builder flatpak.",
"# FLATPAK_USER_DIR works around the sandboxed builder not finding user-installed SDKs.",
"if command -v flatpak-builder >/dev/null 2>&1; then BUILDER='flatpak-builder'; else BUILDER=\"flatpak run --env=FLATPAK_USER_DIR=$HOME/.local/share/flatpak --command=flatpak-builder org.flatpak.Builder\"; fi",
"# Export to a local repo and install from the HOST flatpak (not --install from",
"# inside the builder sandbox): the desktop-file export rewrites Exec to the",
"# path of the installing flatpak binary, and from inside the sandbox that is",
"# /app/bin/flatpak — which doesn't exist on the host, breaking menu launches.",
"cd target/flatpak && $BUILDER --force-clean --disable-rofiles-fuse --repo=repo build-dir io.github.ljufa.rsplayer.yml",
"flatpak --user remote-add --if-not-exists --no-gpg-verify rsplayer-local \"file://$PWD/repo\"",
"flatpak --user install --reinstall --noninteractive rsplayer-local io.github.ljufa.rsplayer || flatpak --user install --noninteractive rsplayer-local io.github.ljufa.rsplayer",
"# Refresh the cached AppStream catalog so software stores (COSMIC Store,",
"# GNOME Software) see the rebuilt metadata/screenshots immediately —",
"# flatpak otherwise refreshes remote catalogs only about once a day.",
"flatpak --user update --appstream rsplayer-local",
]
[tasks.run_flatpak]
description = "Run the locally installed flatpak desktop app"
command = "flatpak"
args = ["run", "io.github.ljufa.rsplayer"]
[tasks.flatpak_bundle]
description = "Create a single-file .flatpak bundle (for testing on other machines/VMs) from the repo produced by build_flatpak"
script = [
"set -e",
"test -d target/flatpak/repo || { echo 'No target/flatpak/repo — run: cargo make build_flatpak'; exit 1; }",
"flatpak build-bundle target/flatpak/repo target/flatpak/rsplayer-${RELEASE_VERSION}.flatpak io.github.ljufa.rsplayer master",
"echo \"Bundle ready: target/flatpak/rsplayer-${RELEASE_VERSION}.flatpak\"",
]
# ── Snap (local build & install) ──────────
# Builds the desktop app snap from the working tree with snapcraft (LXD
# backend). Like the flatpak, the web UI must be pre-built: snapcraft packs
# the tree with dist/web-ui in place. See PKGS/snap/README.md.
[tasks.build_snap]
description = "Build the desktop app snap from the working tree (snapcraft/LXD)"
script = [
"set -e",
"test -f dist/web-ui/public/index.html || { echo 'No dist/web-ui — run: cargo make build_ui_release'; exit 1; }",
"# Stage a clean tree (same trick as build_flatpak): snapcraft copies the",
"# whole source dir into the LXD container and does not respect .gitignore,",
"# so building from the repo root would ship the multi-GB target/ caches",
"# (and choke on root-owned files left by docker builds).",
"rm -rf target/snap/src",
"mkdir -p target/snap/src",
"git ls-files -co --exclude-standard -z | tar --null -T - -cf - | tar -xf - -C target/snap/src",
"cp -r dist target/snap/src/dist",
"(cd target/snap/src && snapcraft pack)",
"mv target/snap/src/rsplayer_*.snap .",
"ls -1 rsplayer_*.snap",
]
[tasks.install_snap]
description = "Install the locally built snap (dangerous mode, unsigned)"
script = [
"set -e",
"snap_file=$(ls -1t rsplayer_*.snap | head -1)",
"test -n \"$snap_file\" || { echo 'No rsplayer_*.snap — run: cargo make build_snap'; exit 1; }",
"sudo snap install --dangerous \"$snap_file\"",
]
[tasks.run_snap]
description = "Run the locally installed snap desktop app"
command = "snap"
args = ["run", "rsplayer"]
# ── BE (Backend) ──────────────────────────
[tasks.build_dev]
description = "Compile backend in dev mode"
command = "cargo"
args = ["build", "--package", "rsplayer", "--bin", "rsplayer"]
[tasks.kill_local]
description = "Kill running rsplayer process"
script = "sudo pkill -9 rsplayer || true"
[tasks.run_local]
description = "Build backend, set capabilities, and run locally"
dependencies = ["kill_local", "build_dev"]
env = { "RUST_LOG" = "rsplayer=info,playback=debug,metadata=info,symphonia=info,warp=info", "RUST_BACKTRACE" = "full"}
script = [
"set -e",
"sudo /usr/bin/setcap 'cap_net_bind_service,cap_sys_boot,cap_sys_nice,cap_sys_admin,cap_dac_read_search+ep' target/debug/rsplayer",
"cd .run && exec ../target/debug/rsplayer",
]
# ── Deploy ────────────────────────────────
# Runs copy_remote_3 and copy_remote_4 as two *separate* cargo-make
# processes. `run_task = { parallel = true }` cannot be used here: parallel
# tasks share one process and cargo-make applies task env blocks as
# process-global variables, so TARGET/TARGET_HOST/CARGO_TARGET_DIR of the
# two targets race and clobber each other.
[tasks.copy_remote]
description = "Build and deploy to targets .3 and .4 in parallel"
script = '''
RC_DIR=$(mktemp -d)
( cargo make copy_remote_3 2>&1; echo $? > "$RC_DIR/rc3" ) | sed -u 's/^/[.3 arm ] /' &
P3=$!
( cargo make copy_remote_4 2>&1; echo $? > "$RC_DIR/rc4" ) | sed -u 's/^/[.4 arm64] /' &
P4=$!
wait $P3
wait $P4
RC3=$(cat "$RC_DIR/rc3" 2>/dev/null || echo 1)
RC4=$(cat "$RC_DIR/rc4" 2>/dev/null || echo 1)
rm -rf "$RC_DIR"
echo "copy_remote_3 exit=$RC3, copy_remote_4 exit=$RC4"
[ "$RC3" = "0" ] && [ "$RC4" = "0" ]
'''
[tasks.build_release_3.env]
TARGET = "arm-unknown-linux-gnueabihf"
# Dedicated per-target dir: parallel builds don't fight over cargo's
# build-dir lock, and since no host build ever touches it, the defensive
# `cargo clean` is unnecessary — deploys build incrementally.
CARGO_TARGET_DIR = { value = "target/cross-arm", condition = { env_not_set = ["CI"] } }
RUST_SYSROOT_LIB = { script = ["echo $(rustc --print sysroot)/lib"] }
CROSS_CONTAINER_OPTS = "-e LD_LIBRARY_PATH=${RUST_SYSROOT_LIB} -v ${RUST_SYSROOT_LIB}:${RUST_SYSROOT_LIB}"
[tasks.build_release_3]
description = "Cross-build release for target .3 (arm)"
script = [
"cross build --target ${TARGET} --release --package rsplayer --bin rsplayer",
]
[tasks.copy_remote_3.env]
TARGET_HOST = "192.168.5.3"
TARGET = "arm-unknown-linux-gnueabihf"
CARGO_TARGET_DIR = { value = "target/cross-arm", condition = { env_not_set = ["CI"] } }
[tasks.copy_remote_3]
description = "Cross-build release and rsync to target .3"
dependencies = ["build_release_3"]
script = [
"rsync -avvP --rsync-path=\"sudo rsync\" ${CARGO_TARGET_DIR}/${TARGET}/release/rsplayer pi@${TARGET_HOST}:/usr/bin",
"ssh -x pi@${TARGET_HOST} \"sudo systemctl restart rsplayer\"",
]
[tasks.build_release_4.env]
TARGET = "aarch64-unknown-linux-gnu"
CARGO_TARGET_DIR = { value = "target/cross-aarch64", condition = { env_not_set = ["CI"] } }
RUST_SYSROOT_LIB = { script = ["echo $(rustc --print sysroot)/lib"] }
CROSS_CONTAINER_OPTS = "-e LD_LIBRARY_PATH=${RUST_SYSROOT_LIB} -v ${RUST_SYSROOT_LIB}:${RUST_SYSROOT_LIB}"
[tasks.build_release_4]
description = "Cross-build release for target .4 (aarch64)"
script = [
"cross build --target ${TARGET} --release --package rsplayer --bin rsplayer",
]
[tasks.copy_remote_4.env]
TARGET_HOST = "192.168.5.4"
TARGET = "aarch64-unknown-linux-gnu"
CARGO_TARGET_DIR = { value = "target/cross-aarch64", condition = { env_not_set = ["CI"] } }
[tasks.copy_remote_4]
description = "Cross-build release and rsync to target .4"
dependencies = ["build_release_4"]
script = [
"rsync -avvP --rsync-path=\"sudo rsync\" ${CARGO_TARGET_DIR}/${TARGET}/release/rsplayer pi@${TARGET_HOST}:/usr/bin",
"ssh -x pi@${TARGET_HOST} \"sudo systemctl restart rsplayer\"",
]
[tasks.copy_remote_dev]
description = "Build dev and rsync to target device"
dependencies = ["build_dev"]
script = [
"rsync -avvP --rsync-path=\"sudo rsync\" target/debug/rsplayer pi@${TARGET_HOST}:/usr/bin",
]
[tasks.package_deb_local.env]
CARGO_TARGET_DIR = { value = "target/cross" }
[tasks.package_deb_local]
description = "Build UI + BE release, package deb, and rsync to target"
dependencies = ["build_ui_release", "build_release", "package_deb_release"]
script = "rsync -avvP ${CARGO_TARGET_DIR}/${TARGET}/debian/*.deb pi@${TARGET_HOST}:~"
[tasks.run_as_rsplayer]
description = "Build release locally and restart systemd service"
script = [
"sudo systemctl stop rsplayer",
"cargo make build_release",
"sudo cp target/x86_64-unknown-linux-gnu/release/rsplayer /usr/bin",
"sudo systemctl start rsplayer",
]
# ═══════════════════════════════════════════
# LINT / TEST / MISC
# ═══════════════════════════════════════════
[tasks.check_toolchain_pins]
description = "Fail when the pinned Rust / dx / wasm-bindgen versions disagree (rust-toolchain.toml, builder + android images, android-build-env.sh, macOS job, Cargo.lock)"
script = '''
status=0
check() {
# $1 = where, $2 = wanted, $3 = found (may be several lines)
if [ -z "$3" ]; then
echo "::error::$1: no version found (pattern out of date?)"; status=1; return
fi
for v in $3; do
if [ "$v" = "$2" ]; then echo "ok $1 $v"
else echo "::error::$1 pins $v, expected $2"; status=1; fi
done
}
rust=$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml)
echo "rust-toolchain.toml: $rust"
check docker/Dockerfile.builder "$rust" "$(sed -n 's/^ARG RUST_VERSION=//p' docker/Dockerfile.builder)"
check docker/Dockerfile.android "$rust" "$(sed -n 's/^ARG RUST_VERSION=//p' docker/Dockerfile.android)"
check crates/desktop/android-build-env.sh "$rust" "$(sed -n 's/^ANDROID_RUST_VERSION=//p' crates/desktop/android-build-env.sh)"
check ".github/workflows/cd.yml (macOS)" "$rust" "$(sed -n 's/^ *toolchain: //p' .github/workflows/cd.yml)"
dx=$(awk '/^name = "dioxus"$/ {getline; gsub(/version = |"/, ""); print; exit}' Cargo.lock)
echo "dioxus (Cargo.lock): $dx"
check docker/Dockerfile.builder "$dx" "$(sed -n 's/^ARG DX_VERSION=//p' docker/Dockerfile.builder)"
check crates/desktop/android-build-env.sh "$dx" "$(sed -n 's/^ANDROID_DX_VERSION=//p' crates/desktop/android-build-env.sh)"
# The Android image installs wasm-bindgen-cli itself (NO_DOWNLOADS); it must equal the crate exactly.
wb=$(awk '/^name = "wasm-bindgen"$/ {getline; gsub(/version = |"/, ""); print; exit}' Cargo.lock)
echo "wasm-bindgen (Cargo.lock): $wb"
check docker/Dockerfile.android "$wb" "$(sed -n 's/^ARG WASM_BINDGEN_VERSION=//p' docker/Dockerfile.android)"
exit $status
'''
[tasks.test_ci]
description = "Run all tests (creates stub dist/web-ui/public so RustEmbed compiles without a UI build)"
script = [
"mkdir -p dist/web-ui/public",
"touch dist/web-ui/public/index.html",
"cargo test --all-features",
]
[tasks.clippy]
description = "Lint with Clippy (fix + deny warnings)"
install_crate = { rustup_component_name = "clippy", binary = "cargo-clippy", test_arg = "--help" }
command = "cargo"
args = [
"clippy",
"--fix",
"--allow-dirty",
"--allow-staged",
"--all-features",
"--",
"--deny", "warnings",
"--deny", "clippy::pedantic",
"--deny", "clippy::nursery",
"--allow", "clippy::use_self",
"--allow", "clippy::module_name_repetitions",
"--allow", "clippy::must_use_candidate",
"--allow", "let_underscore_drop",
"--allow", "clippy::missing_panics_doc",
"--allow", "clippy::missing_errors_doc",
"--allow", "clippy::use_self",
"--allow", "clippy::significant-drop-tightening",
"--allow", "clippy::similar_names",
]
[tasks.test_in_cross]
command = "cross"
args = ["test", "--target", "${TARGET}"]
[tasks.test_in_cross.env]
RUST_SYSROOT_LIB = { script = ["echo $(rustc --print sysroot)/lib"] }
CROSS_CONTAINER_OPTS = "-e LD_LIBRARY_PATH=${RUST_SYSROOT_LIB} -v ${RUST_SYSROOT_LIB}:${RUST_SYSROOT_LIB}"
[tasks.tag]
script = [
"git tag ${RELEASE_VERSION} ${RELEASE_BRANCH}",
"git push originpublic ${RELEASE_VERSION}",
]