#!/usr/bin/env bash set -euo pipefail export MACOSX_DEPLOYMENT_TARGET="12.0" cd "$(git rev-parse --show-toplevel)" PLUGIN_NAME="cagire-plugins" LIB_NAME="cagire_plugins" # cargo converts hyphens to underscores OUT="releases" PLATFORMS=( "aarch64-apple-darwin" "x86_64-apple-darwin" "x86_64-unknown-linux-gnu" "aarch64-unknown-linux-gnu" "x86_64-pc-windows-gnu" ) PLATFORM_LABELS=( "macOS aarch64 (native)" "macOS x86_64 (native)" "Linux x86_64 (cross)" "Linux aarch64 (cross)" "Windows x86_64 (cross)" ) PLATFORM_ALIASES=( "macos-arm64" "macos-x86_64" "linux-x86_64" "linux-aarch64" "windows-x86_64" ) # --- CLI argument parsing --- cli_platforms="" cli_targets="" cli_yes=false cli_all=false while [[ $# -gt 0 ]]; do case "$1" in --platforms) cli_platforms="$2"; shift 2 ;; --targets) cli_targets="$2"; shift 2 ;; --yes) cli_yes=true; shift ;; --all) cli_all=true; shift ;; -h|--help) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --platforms Comma-separated: macos-arm64,macos-x86_64,linux-x86_64,linux-aarch64,windows-x86_64" echo " --targets Comma-separated: cli,desktop,plugins" echo " --all Build all platforms and targets" echo " --yes Skip confirmation prompt" echo "" echo "Without options, runs interactively." exit 0 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done resolve_platform_alias() { local alias="$1" for i in "${!PLATFORM_ALIASES[@]}"; do if [[ "${PLATFORM_ALIASES[$i]}" == "$alias" ]]; then echo "$i" return fi done echo "Unknown platform: $alias" >&2 exit 1 } # --- Helpers --- prompt_platforms() { echo "Select platform (0=all, comma-separated):" echo " 0) All" for i in "${!PLATFORMS[@]}"; do echo " $((i+1))) ${PLATFORM_LABELS[$i]}" done read -rp "> " choice if [[ "$choice" == "0" || -z "$choice" ]]; then selected_platforms=("${PLATFORMS[@]}") selected_labels=("${PLATFORM_LABELS[@]}") else IFS=',' read -ra indices <<< "$choice" selected_platforms=() selected_labels=() for idx in "${indices[@]}"; do idx="${idx// /}" idx=$((idx - 1)) if (( idx < 0 || idx >= ${#PLATFORMS[@]} )); then echo "Invalid platform index: $((idx+1))" exit 1 fi selected_platforms+=("${PLATFORMS[$idx]}") selected_labels+=("${PLATFORM_LABELS[$idx]}") done fi } prompt_targets() { echo "" echo "Select targets (0=all, comma-separated):" echo " 0) All" echo " 1) cagire" echo " 2) cagire-desktop" echo " 3) cagire-plugins (CLAP/VST3)" read -rp "> " choice build_cagire=false build_desktop=false build_plugins=false if [[ "$choice" == "0" || -z "$choice" ]]; then build_cagire=true build_desktop=true build_plugins=true else IFS=',' read -ra targets <<< "$choice" for t in "${targets[@]}"; do t="${t// /}" case "$t" in 1) build_cagire=true ;; 2) build_desktop=true ;; 3) build_plugins=true ;; *) echo "Invalid target: $t"; exit 1 ;; esac done fi } confirm_summary() { echo "" echo "=== Build Summary ===" echo "" echo "Platforms:" for label in "${selected_labels[@]}"; do echo " - $label" done echo "" echo "Targets:" $build_cagire && echo " - cagire" $build_desktop && echo " - cagire-desktop" $build_plugins && echo " - cagire-plugins (CLAP/VST3)" echo "" read -rp "Proceed? [Y/n] " yn case "${yn,,}" in n|no) echo "Aborted."; exit 0 ;; esac } platform_os() { case "$1" in *windows*) echo "windows" ;; *linux*) echo "linux" ;; *apple*) echo "macos" ;; esac } platform_arch() { case "$1" in aarch64*) echo "aarch64" ;; x86_64*) echo "x86_64" ;; esac } platform_suffix() { case "$1" in *windows*) echo ".exe" ;; *) echo "" ;; esac } is_cross_target() { case "$1" in *linux*|*windows*) return 0 ;; *) return 1 ;; esac } native_target() { [[ "$1" == "aarch64-apple-darwin" ]] } release_dir() { if native_target "$1"; then echo "target/release" else echo "target/$1/release" fi } target_flag() { if native_target "$1"; then echo "" else echo "--target $1" fi } builder_for() { if is_cross_target "$1"; then echo "cross" else echo "cargo" fi } build_binary() { local platform="$1" shift local builder builder=$(builder_for "$platform") local tf tf=$(target_flag "$platform") # shellcheck disable=SC2086 $builder build --release $tf "$@" } bundle_plugins_native() { local platform="$1" local tf tf=$(target_flag "$platform") # shellcheck disable=SC2086 cargo xtask bundle "$PLUGIN_NAME" --release $tf } bundle_desktop_native() { local platform="$1" local tf tf=$(target_flag "$platform") # shellcheck disable=SC2086 cargo bundle --release --features desktop --bin cagire-desktop $tf } bundle_plugins_cross() { local platform="$1" shift local rd rd=$(release_dir "$platform") local os os=$(platform_os "$platform") local arch arch=$(platform_arch "$platform") # Build the cdylib with cross # shellcheck disable=SC2086 build_binary "$platform" -p "$PLUGIN_NAME" "$@" # Determine source library file local src_lib case "$os" in linux) src_lib="$rd/lib${LIB_NAME}.so" ;; windows) src_lib="$rd/${LIB_NAME}.dll" ;; esac if [[ ! -f "$src_lib" ]]; then echo " ERROR: Expected library not found: $src_lib" return 1 fi # Assemble CLAP bundle (flat file) local clap_out="$OUT/${PLUGIN_NAME}-${os}-${arch}.clap" cp "$src_lib" "$clap_out" echo " CLAP -> $clap_out" # Assemble VST3 bundle (directory tree) local vst3_dir="$OUT/${PLUGIN_NAME}-${os}-${arch}.vst3" local vst3_contents case "$os" in linux) vst3_contents="$vst3_dir/Contents/${arch}-linux" mkdir -p "$vst3_contents" cp "$src_lib" "$vst3_contents/${PLUGIN_NAME}.so" ;; windows) vst3_contents="$vst3_dir/Contents/${arch}-win" mkdir -p "$vst3_contents" cp "$src_lib" "$vst3_contents/${PLUGIN_NAME}.vst3" ;; esac echo " VST3 -> $vst3_dir/" } copy_artifacts() { local platform="$1" local rd rd=$(release_dir "$platform") local os os=$(platform_os "$platform") local arch arch=$(platform_arch "$platform") local suffix suffix=$(platform_suffix "$platform") if $build_cagire; then local src="$rd/cagire${suffix}" local dst="$OUT/cagire-${os}-${arch}${suffix}" cp "$src" "$dst" echo " cagire -> $dst" fi if $build_desktop; then local src="$rd/cagire-desktop${suffix}" local dst="$OUT/cagire-desktop-${os}-${arch}${suffix}" cp "$src" "$dst" echo " cagire-desktop -> $dst" # macOS .app bundle if [[ "$os" == "macos" ]]; then local app_src="$rd/bundle/osx/Cagire.app" if [[ ! -d "$app_src" ]]; then echo " ERROR: .app bundle not found at $app_src" echo " Did 'cargo bundle' succeed?" return 1 fi local app_dst="$OUT/Cagire-${arch}.app" rm -rf "$app_dst" cp -R "$app_src" "$app_dst" echo " Cagire.app -> $app_dst" scripts/make-dmg.sh "$app_dst" "$OUT" fi fi # NSIS installer for Windows targets if [[ "$os" == "windows" ]] && command -v makensis &>/dev/null; then echo " Building NSIS installer..." local version version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') local abs_root abs_root=$(pwd) makensis -DVERSION="$version" \ -DCLI_EXE="$abs_root/$rd/cagire.exe" \ -DDESKTOP_EXE="$abs_root/$rd/cagire-desktop.exe" \ -DICON="$abs_root/assets/Cagire.ico" \ -DOUTDIR="$abs_root/$OUT" \ nsis/cagire.nsi echo " Installer -> $OUT/cagire-${version}-windows-x86_64-setup.exe" fi # AppImage for Linux targets if [[ "$os" == "linux" ]]; then if $build_cagire; then scripts/make-appimage.sh "$rd/cagire" "$arch" "$OUT" fi if $build_desktop; then scripts/make-appimage.sh "$rd/cagire-desktop" "$arch" "$OUT" fi fi # Plugin artifacts for native targets (cross handled in bundle_plugins_cross) if $build_plugins && ! is_cross_target "$platform"; then local bundle_dir="target/bundled" # CLAP local clap_src="$bundle_dir/${PLUGIN_NAME}.clap" if [[ -e "$clap_src" ]]; then local clap_dst="$OUT/${PLUGIN_NAME}-${os}-${arch}.clap" cp -r "$clap_src" "$clap_dst" echo " CLAP -> $clap_dst" fi # VST3 local vst3_src="$bundle_dir/${PLUGIN_NAME}.vst3" if [[ -d "$vst3_src" ]]; then local vst3_dst="$OUT/${PLUGIN_NAME}-${os}-${arch}.vst3" rm -rf "$vst3_dst" cp -r "$vst3_src" "$vst3_dst" echo " VST3 -> $vst3_dst/" fi fi } # --- Main --- if $cli_all; then selected_platforms=("${PLATFORMS[@]}") selected_labels=("${PLATFORM_LABELS[@]}") build_cagire=true build_desktop=true build_plugins=true elif [[ -n "$cli_platforms" || -n "$cli_targets" ]]; then # Resolve platforms from CLI if [[ -n "$cli_platforms" ]]; then selected_platforms=() selected_labels=() IFS=',' read -ra aliases <<< "$cli_platforms" for alias in "${aliases[@]}"; do alias="${alias// /}" idx=$(resolve_platform_alias "$alias") selected_platforms+=("${PLATFORMS[$idx]}") selected_labels+=("${PLATFORM_LABELS[$idx]}") done else selected_platforms=("${PLATFORMS[@]}") selected_labels=("${PLATFORM_LABELS[@]}") fi # Resolve targets from CLI build_cagire=false build_desktop=false build_plugins=false if [[ -n "$cli_targets" ]]; then IFS=',' read -ra tgts <<< "$cli_targets" for t in "${tgts[@]}"; do t="${t// /}" case "$t" in cli) build_cagire=true ;; desktop) build_desktop=true ;; plugins) build_plugins=true ;; *) echo "Unknown target: $t (expected: cli, desktop, plugins)"; exit 1 ;; esac done else build_cagire=true build_desktop=true build_plugins=true fi else prompt_platforms prompt_targets fi if ! $cli_yes && [[ -z "$cli_platforms" ]] && ! $cli_all; then confirm_summary fi mkdir -p "$OUT" step=0 total=${#selected_platforms[@]} for platform in "${selected_platforms[@]}"; do step=$((step + 1)) echo "" echo "=== [$step/$total] $platform ===" # Enable ASIO for Windows targets local asio_flag="" case "$platform" in *windows*) asio_flag="--features asio" ;; esac if $build_cagire; then echo " -> cagire" # shellcheck disable=SC2086 build_binary "$platform" $asio_flag fi if $build_desktop; then echo " -> cagire-desktop" if [[ -n "$asio_flag" ]]; then build_binary "$platform" --features desktop,asio --bin cagire-desktop else build_binary "$platform" --features desktop --bin cagire-desktop fi if ! is_cross_target "$platform"; then echo " -> bundling cagire-desktop .app" bundle_desktop_native "$platform" fi fi if $build_plugins; then echo " -> cagire-plugins" if is_cross_target "$platform"; then bundle_plugins_cross "$platform" $asio_flag else bundle_plugins_native "$platform" fi fi echo " Copying artifacts..." copy_artifacts "$platform" done echo "" echo "=== Done ===" echo "" ls -lhR "$OUT/"