#!/usr/bin/env bash
#
# Build release artifacts for all platforms.
#
# Usage: scripts/release [OPTIONS]
#
# Options:
# --only-dmg Build macOS aarch64 DMG only
# --only-deb Build Linux amd64 deb only
# --only-appimage Build Linux amd64 AppImage only
# --rebuild-image Force rebuild of podman container images
# --clean Wipe target-release/, node_modules/ and Linux build volumes
# --help Show this help
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# ---- Flags ----
BUILD_DMG=true
BUILD_DEB=true
BUILD_APPIMAGE=true
REBUILD_IMAGE=false
CLEAN=false
for arg in "$@"; do
case "$arg" in
--only-dmg) BUILD_DMG=true; BUILD_DEB=false; BUILD_APPIMAGE=false ;;
--only-deb) BUILD_DMG=false; BUILD_DEB=true; BUILD_APPIMAGE=false ;;
--only-appimage) BUILD_DMG=false; BUILD_DEB=false; BUILD_APPIMAGE=true ;;
--rebuild-image) REBUILD_IMAGE=true ;;
--clean) CLEAN=true; BUILD_DMG=false; BUILD_DEB=false; BUILD_APPIMAGE=false ;;
--help|-h)
head -20 "$0" | grep '^#' | sed 's/^# \?//'
exit 0
;;
*) echo "Unknown option: $arg" >&2; exit 1 ;;
esac
done
# ---- Helpers ----
step() { echo; echo "==> $*"; }
# Builds a container image if it does not already exist (or --rebuild-image).
# $1: image name, $2: dockerfile path (relative to PROJECT_DIR), $3: platform
ensure_image() {
local name="$1"
local dockerfile="$2"
local platform="$3"
if [[ "$REBUILD_IMAGE" == true ]] || ! podman image exists "$name" 2>/dev/null; then
echo " Building container image: $name"
podman build \
--platform "$platform" \
-t "$name" \
-f "$PROJECT_DIR/$dockerfile" \
"$PROJECT_DIR"
fi
}
# ---- Version and output directory ----
VERSION="$(jq -r '.version' "$PROJECT_DIR/crates/radicle-tauri/tauri.conf.json")"
RELEASE_SHA="$(git -C "$PROJECT_DIR" rev-parse HEAD)"
SHORT_SHA="$(git -C "$PROJECT_DIR" rev-parse --short=8 HEAD)"
RELEASE_DIR="$PROJECT_DIR/releases/v$VERSION"
step "Building release v$VERSION (${SHORT_SHA})"
echo " Output: $RELEASE_DIR"
mkdir -p "$RELEASE_DIR"
# ---- Clean ----
if [[ "$CLEAN" == true ]]; then
step "Cleaning build artifacts"
rm -rf "$PROJECT_DIR/target-release"
rm -rf "$PROJECT_DIR/node_modules"
podman volume rm radicle-desktop-linux-target radicle-desktop-linux-nm radicle-desktop-linux-cargo 2>/dev/null || true
podman image rm radicle-desktop-release-builder 2>/dev/null || true
echo " Cleaned"
fi
# ---- macOS aarch64 DMG ----
if [[ "$BUILD_DMG" == true ]]; then
step "Building macOS aarch64 DMG"
cd "$PROJECT_DIR"
npm install
CARGO_TARGET_DIR="$PROJECT_DIR/target-release" \
npm exec -- tauri build --bundles dmg
cp "target-release/release/bundle/dmg/Radicle_${VERSION}_aarch64.dmg" \
"$RELEASE_DIR/radicle-desktop-aarch64.dmg"
echo " radicle-desktop-aarch64.dmg"
fi
# ---- Linux amd64 deb ----
#
# Runs in the native ARM64 Ubuntu container, which cross-compiles Rust to
# x86_64. Build artifacts are cached in named podman volumes so subsequent
# runs only recompile what changed.
#
# When to clear volumes:
# After a Rust toolchain bump or dependency change that confuses incremental
# compilation: podman volume rm radicle-desktop-linux-target radicle-desktop-linux-cargo
# After a major npm dependency change: podman volume rm radicle-desktop-linux-nm
if [[ "$BUILD_DEB" == true ]]; then
step "Building Linux amd64 deb"
ensure_image "radicle-desktop-release-builder" "Dockerfile.release" "linux/arm64"
podman run --rm \
-v "$PROJECT_DIR:/workspace:z" \
-v radicle-desktop-linux-target:/workspace/target:z \
-v radicle-desktop-linux-nm:/workspace/node_modules:z \
-v radicle-desktop-linux-cargo:/root/.cargo/registry:z \
-v "$RELEASE_DIR:/output:z" \
-w /workspace \
-e CI=true \
radicle-desktop-release-builder \
bash -c "
set -euo pipefail
npm ci
npm exec -- tauri build --target x86_64-unknown-linux-gnu --bundles deb
cp target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb /output/
"
echo " radicle-desktop_${VERSION}_amd64.deb"
fi
# ---- Linux amd64 AppImage ----
#
# See scripts/appimage-build for the full setup. Key points:
# - --privileged to mount binfmt_misc for x86_64 emulation via QEMU
# - APPIMAGE_EXTRACT_AND_RUN=1 so linuxdeploy-x86_64.AppImage runs without FUSE
# - PKG_CONFIG_PATH for the GTK plugin to find amd64 packages
# - PATH includes libgtk-3-0t64 dir where gtk-query-immodules-3.0 lives
# The script is mounted from the workspace (heredocs pipe to host, not container)
if [[ "$BUILD_APPIMAGE" == true ]]; then
step "Building Linux amd64 AppImage"
ensure_image "radicle-desktop-release-builder" "Dockerfile.release" "linux/arm64"
podman run --rm \
--privileged \
-v "$PROJECT_DIR:/workspace:z" \
-v radicle-desktop-linux-target:/workspace/target:z \
-v radicle-desktop-linux-nm:/workspace/node_modules:z \
-v radicle-desktop-linux-cargo:/root/.cargo/registry:z \
-v "$RELEASE_DIR:/output:z" \
-w /workspace \
-e CI=true \
radicle-desktop-release-builder \
bash /workspace/scripts/appimage-build
echo " radicle-desktop_${VERSION}_amd64.AppImage"
fi
if [[ "$CLEAN" == false ]]; then
# ---- latest.json ----
step "Generating latest.json"
printf '{"sha": "%s", "version": "%s"}' "$RELEASE_SHA" "$VERSION" \
> "$RELEASE_DIR/latest.json"
echo " latest.json"
# ---- Summary ----
echo
echo "Done. Artifacts in $RELEASE_DIR:"
ls -lh "$RELEASE_DIR"
fi