Ctrl K
ComunidadGuías…

Makefile.toml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[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}",
]