Acord/scripts/macos/package.sh

274 lines
8.5 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# cross-compile + zip distributables for all six platform targets from a macOS host
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
source "$ROOT/scripts/_build-dirs.sh"
cd "$ROOT"
case "$(uname -s)" in
Darwin) ;;
*) echo "package.sh: macOS host only (need swiftc + codesign)" >&2; exit 1;;
esac
# raise per-process file descriptor limit
ulimit -n 65536 2>/dev/null || ulimit -n 8192 2>/dev/null || true
ALL_TARGETS=(
macos-aarch64
macos-x86_64
windows-aarch64
windows-x86_64
linux-aarch64
linux-x86_64
)
usage() {
cat >&2 <<EOF
usage: cargo xtask package --all
cargo xtask package --target <name> [--target <name> ...]
targets: ${ALL_TARGETS[*]}
EOF
exit 2
}
TARGETS=()
while [ $# -gt 0 ]; do
case "$1" in
--all) TARGETS=("${ALL_TARGETS[@]}"); shift ;;
--target) [ $# -ge 2 ] || usage; TARGETS+=("$2"); shift 2 ;;
-h|--help) usage ;;
*) echo "unknown arg: $1" >&2; usage ;;
esac
done
[ ${#TARGETS[@]} -eq 0 ] && usage
need() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: $1 not found. $2" >&2; exit 1; }; }
need rustup "install rustup from https://rustup.rs"
need swiftc "install Xcode Command Line Tools (xcode-select --install)"
need zip "comes with macOS"
NEEDS_ZIG=0
for t in "${TARGETS[@]}"; do
case "$t" in windows-*|linux-*) NEEDS_ZIG=1 ;; esac
done
if [ $NEEDS_ZIG -eq 1 ]; then
need zig "brew install zig"
need cargo-zigbuild "cargo install cargo-zigbuild"
fi
PKG="$ROOT/build/package"
DIST="$ROOT/dist"
mkdir -p "$PKG" "$DIST"
# generate a 256px PNG for Windows + Linux icons
ICON_PNG="$ROOT/build/icon.png"
if [ ! -f "$ICON_PNG" ] || [ "$ROOT/assets/Acord.svg" -nt "$ICON_PNG" ]; then
if command -v rsvg-convert >/dev/null 2>&1 && [ -f "$ROOT/assets/Acord.svg" ]; then
rsvg-convert --width 256 --height 256 "$ROOT/assets/Acord.svg" -o "$ICON_PNG"
fi
fi
ensure_icns() {
local icns="$ROOT/build/AppIcon.icns"
if [ -f "$icns" ] && [ "$ROOT/assets/Acord.svg" -ot "$icns" ]; then return; fi
[ -f "$ROOT/assets/Acord.svg" ] || return 0
command -v rsvg-convert >/dev/null 2>&1 || return 0
command -v iconutil >/dev/null 2>&1 || return 0
local iconset="$ROOT/build/AppIcon.iconset"
rm -rf "$iconset"
mkdir -p "$iconset"
for size in 16 32 64 128 256 512 1024; do
rsvg-convert --width="$size" --height="$size" \
"$ROOT/assets/Acord.svg" -o "$iconset/icon_${size}.png"
done
cp "$iconset/icon_16.png" "$iconset/icon_16x16.png"
cp "$iconset/icon_32.png" "$iconset/icon_16x16@2x.png"
cp "$iconset/icon_32.png" "$iconset/icon_32x32.png"
cp "$iconset/icon_64.png" "$iconset/icon_32x32@2x.png"
cp "$iconset/icon_128.png" "$iconset/icon_128x128.png"
cp "$iconset/icon_256.png" "$iconset/icon_128x128@2x.png"
cp "$iconset/icon_256.png" "$iconset/icon_256x256.png"
cp "$iconset/icon_512.png" "$iconset/icon_256x256@2x.png"
cp "$iconset/icon_512.png" "$iconset/icon_512x512.png"
cp "$iconset/icon_1024.png" "$iconset/icon_512x512@2x.png"
rm -f "$iconset"/icon_16.png "$iconset"/icon_32.png "$iconset"/icon_64.png \
"$iconset"/icon_128.png "$iconset"/icon_256.png "$iconset"/icon_512.png \
"$iconset"/icon_1024.png
iconutil -c icns "$iconset" -o "$icns"
rm -rf "$iconset"
}
zip_target() {
local target="$1" path="$2"
local out="$DIST/acord-${target}.zip"
rm -f "$out"
(cd "$(dirname "$path")" && zip -r -q "$out" "$(basename "$path")")
echo "$out ($(du -h "$out" | cut -f1))"
}
build_macos() {
local arch="$1" rust_target swift_target
case "$arch" in
aarch64) rust_target=aarch64-apple-darwin; swift_target=arm64-apple-macosx14.0 ;;
x86_64) rust_target=x86_64-apple-darwin; swift_target=x86_64-apple-macosx14.0 ;;
esac
rustup target add "$rust_target" >/dev/null 2>&1 || true
ensure_icns
echo "==> macOS $arch ($rust_target / $swift_target)"
export MACOSX_DEPLOYMENT_TARGET=14.0
export ZERO_AR_DATE=0
cargo build --release -p acord-viewport --target "$rust_target"
local rust_lib="$CARGO_TARGET_DIR/$rust_target/release"
[ -f "$rust_lib/libacord_viewport.a" ] \
|| { echo "ERROR: libacord_viewport.a missing for $rust_target" >&2; exit 1; }
local stage="$PKG/macos-${arch}"
local app="$stage/Acord.app"
rm -rf "$stage"
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
cp "$ROOT/macos/Info.plist" "$app/Contents/Info.plist"
[ -f "$ROOT/build/AppIcon.icns" ] && cp "$ROOT/build/AppIcon.icns" "$app/Contents/Resources/AppIcon.icns"
local sdk
sdk=$(xcrun --show-sdk-path)
swiftc \
-target "$swift_target" \
-sdk "$sdk" \
-import-objc-header "$ROOT/viewport/include/acord.h" \
-L "$rust_lib" -lacord_viewport \
-framework Cocoa -framework SwiftUI \
-framework Metal -framework MetalKit \
-framework QuartzCore -framework CoreGraphics -framework CoreFoundation \
-O \
-o "$app/Contents/MacOS/Acord" \
"$ROOT"/macos/src/*.swift
codesign --force --sign - "$app"
zip_target "macos-${arch}" "$app"
}
build_windows() {
local arch="$1" rust_target
case "$arch" in
aarch64) rust_target=aarch64-pc-windows-gnullvm ;;
x86_64) rust_target=x86_64-pc-windows-gnu ;;
esac
rustup target add "$rust_target" >/dev/null 2>&1 || true
echo "==> Windows $arch ($rust_target via cargo-zigbuild)"
cargo zigbuild --release -p acord-windows --target "$rust_target"
local stage="$PKG/windows-${arch}/Acord"
rm -rf "$stage"
mkdir -p "$stage"
cp "$CARGO_TARGET_DIR/$rust_target/release/acord.exe" "$stage/Acord.exe"
[ -f "$ICON_PNG" ] && cp "$ICON_PNG" "$stage/icon.png"
[ -f "$ROOT/LICENCE" ] && cp "$ROOT/LICENCE" "$stage/LICENCE"
[ -f "$ROOT/README.md" ] && cp "$ROOT/README.md" "$stage/README.md"
zip_target "windows-${arch}" "$stage"
}
build_linux() {
local arch="$1" rust_target
case "$arch" in
aarch64) rust_target=aarch64-unknown-linux-gnu ;;
x86_64) rust_target=x86_64-unknown-linux-gnu ;;
esac
rustup target add "$rust_target" >/dev/null 2>&1 || true
echo "==> Linux $arch (${rust_target}.2.17 via cargo-zigbuild — both x11+wayland linked)"
# .2.17 suffix pins glibc 2.17 (CentOS 7 baseline)
cargo zigbuild --release -p acord-linux --target "${rust_target}.2.17"
local stage="$PKG/linux-${arch}/acord"
rm -rf "$stage"
mkdir -p "$stage"
cp "$CARGO_TARGET_DIR/$rust_target/release/acord" "$stage/Acord"
chmod +x "$stage/Acord"
[ -f "$ICON_PNG" ] && cp "$ICON_PNG" "$stage/icon.png"
[ -f "$ROOT/LICENCE" ] && cp "$ROOT/LICENCE" "$stage/LICENCE"
[ -f "$ROOT/README.md" ] && cp "$ROOT/README.md" "$stage/README.md"
# single-quoted heredoc preserves $ expansions for the generated install script
cat > "$stage/install.sh" <<'INSTALLER_EOF'
#!/usr/bin/env bash
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}"
APP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
ICON_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/256x256/apps"
mkdir -p "$BIN_DIR" "$APP_DIR" "$ICON_DIR"
pkill -x Acord 2>/dev/null || true
sleep 0.2
install -m 755 "$HERE/Acord" "$BIN_DIR/Acord"
[ -f "$HERE/icon.png" ] && install -m 644 "$HERE/icon.png" "$ICON_DIR/acord.png"
cat > "$APP_DIR/acord.desktop" <<DESKTOP
[Desktop Entry]
Type=Application
Name=Acord
Comment=Native markdown editor with Cordial expressions and tables
Exec=$BIN_DIR/Acord %F
Icon=acord
Terminal=false
Categories=Utility;TextEditor;Office;
MimeType=text/markdown;text/plain;
DESKTOP
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database "$APP_DIR" >/dev/null 2>&1 || true
fi
echo "Installed:"
echo " binary → $BIN_DIR/Acord"
echo " icon → $ICON_DIR/acord.png"
echo " desktop → $APP_DIR/acord.desktop"
case ":$PATH:" in
*":$BIN_DIR:"*) ;;
*) echo "note: $BIN_DIR is not on your PATH" >&2 ;;
esac
INSTALLER_EOF
chmod +x "$stage/install.sh"
zip_target "linux-${arch}" "$stage"
}
echo "packaging: ${TARGETS[*]}"
echo
for t in "${TARGETS[@]}"; do
case "$t" in
macos-aarch64) build_macos aarch64 ;;
macos-x86_64) build_macos x86_64 ;;
windows-aarch64) build_windows aarch64 ;;
windows-x86_64) build_windows x86_64 ;;
linux-aarch64) build_linux aarch64 ;;
linux-x86_64) build_linux x86_64 ;;
*) echo "unknown target: $t (valid: ${ALL_TARGETS[*]})" >&2; exit 2 ;;
esac
done
echo
echo "done. dist:"
ls -lh "$DIST"