Initial commit
This commit is contained in:
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#!/bin/zsh
|
||||
# Build, package, install, and launch the AI Music Studio macOS application.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
APP_DISPLAY_NAME="${APP_DISPLAY_NAME:-AI Music Studio}"
|
||||
EXECUTABLE_NAME="${EXECUTABLE_NAME:-music-assistant}"
|
||||
BUNDLE_IDENTIFIER="${BUNDLE_IDENTIFIER:-com.musicassistant.app}"
|
||||
APP_BUNDLE_NAME="${APP_DISPLAY_NAME}.app"
|
||||
BUILD_DIR="${PROJECT_ROOT}/.build/app"
|
||||
APP_BUNDLE="${BUILD_DIR}/${APP_BUNDLE_NAME}"
|
||||
INSTALL_DIR="${INSTALL_DIR:-/Applications}"
|
||||
INSTALL_PATH="${INSTALL_DIR}/${APP_BUNDLE_NAME}"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
|
||||
fail() {
|
||||
print -u2 -- "Error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
step() {
|
||||
print -- "==> $1"
|
||||
}
|
||||
|
||||
find_app_icon() {
|
||||
if [[ -n "${APP_ICON:-}" ]]; then
|
||||
[[ -f "${APP_ICON}" ]] || fail "App icon was not found at ${APP_ICON}."
|
||||
print -- "${APP_ICON}"
|
||||
return
|
||||
fi
|
||||
|
||||
local candidate
|
||||
for candidate in \
|
||||
"${PROJECT_ROOT}/Resources/AI Music Studio.icns" \
|
||||
"${PROJECT_ROOT}/Resources/AppIcon.icns" \
|
||||
"${PROJECT_ROOT}/Resources/MusicAssistant.icns"; do
|
||||
if [[ -f "${candidate}" ]]; then
|
||||
print -- "${candidate}"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
[[ -f "${PROJECT_ROOT}/Package.swift" ]] || fail "Package.swift was not found in ${PROJECT_ROOT}."
|
||||
command -v swift >/dev/null 2>&1 || fail "Swift is required. Install Xcode or the Xcode Command Line Tools."
|
||||
command -v codesign >/dev/null 2>&1 || fail "codesign is required and should be available with Xcode."
|
||||
command -v ditto >/dev/null 2>&1 || fail "ditto is required and should be available on macOS."
|
||||
command -v open >/dev/null 2>&1 || fail "open is required and should be available on macOS."
|
||||
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
step "Stopping any running ${APP_DISPLAY_NAME} application"
|
||||
if pgrep -x "${EXECUTABLE_NAME}" >/dev/null 2>&1; then
|
||||
pkill -x "${EXECUTABLE_NAME}" || fail "Could not stop the running ${APP_DISPLAY_NAME} application."
|
||||
for _ in {1..20}; do
|
||||
pgrep -x "${EXECUTABLE_NAME}" >/dev/null 2>&1 || break
|
||||
sleep 0.1
|
||||
done
|
||||
pgrep -x "${EXECUTABLE_NAME}" >/dev/null 2>&1 && fail "${APP_DISPLAY_NAME} is still running. Quit it and retry."
|
||||
fi
|
||||
|
||||
step "Removing stale installed copy"
|
||||
if [[ -e "${INSTALL_PATH}" ]]; then
|
||||
rm -rf "${INSTALL_PATH}" || fail "Could not remove ${INSTALL_PATH}. Check permissions and retry."
|
||||
fi
|
||||
|
||||
step "Cleaning local Swift build artifacts"
|
||||
swift package clean || fail "Swift Package Manager could not clean the local build artifacts."
|
||||
|
||||
step "Building ${EXECUTABLE_NAME} in release mode"
|
||||
BIN_PATH="$(swift build -c release --show-bin-path 2>/dev/null | tail -n1)"
|
||||
SOURCE_EXECUTABLE="${BIN_PATH}/${EXECUTABLE_NAME}"
|
||||
|
||||
built=0
|
||||
for attempt in 1 2 3; do
|
||||
swift build -c release || fail "Release build failed. Review the Swift compiler diagnostics above."
|
||||
if [[ -x "${SOURCE_EXECUTABLE}" ]]; then
|
||||
built=1
|
||||
break
|
||||
fi
|
||||
print -- "==> Executable not present after build (attempt ${attempt}); retrying"
|
||||
sleep 1
|
||||
done
|
||||
[[ "${built}" -eq 1 ]] || fail "Built executable was not found at ${SOURCE_EXECUTABLE}."
|
||||
|
||||
step "Packaging ${APP_BUNDLE_NAME}"
|
||||
rm -rf "${APP_BUNDLE}"
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
cp "${SOURCE_EXECUTABLE}" "${MACOS_DIR}/${EXECUTABLE_NAME}"
|
||||
|
||||
ICON_PATH="$(find_app_icon)"
|
||||
PLIST_ICON_ENTRY=""
|
||||
if [[ -n "${ICON_PATH}" ]]; then
|
||||
ICON_FILE_NAME="$(basename "${ICON_PATH}")"
|
||||
ICON_FILE_BASE="${ICON_FILE_NAME:r}"
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/${ICON_FILE_NAME}"
|
||||
PLIST_ICON_ENTRY=$' <key>CFBundleIconFile</key>\n <string>'"${ICON_FILE_BASE}"$'</string>'
|
||||
else
|
||||
step "No app icon found; packaging without a custom icon"
|
||||
fi
|
||||
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_DISPLAY_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
${PLIST_ICON_ENTRY}
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_DISPLAY_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>14.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
plutil -lint "${CONTENTS_DIR}/Info.plist" >/dev/null || fail "Generated Info.plist is invalid."
|
||||
codesign --force --sign - --timestamp=none "${APP_BUNDLE}" || fail "Ad-hoc signing of ${APP_BUNDLE_NAME} failed."
|
||||
codesign --verify --deep --strict "${APP_BUNDLE}" || fail "Packaged ${APP_BUNDLE_NAME} failed code-signature verification."
|
||||
[[ -x "${MACOS_DIR}/${EXECUTABLE_NAME}" ]] || fail "Packaged executable is missing."
|
||||
|
||||
step "Installing ${APP_BUNDLE_NAME} in ${INSTALL_DIR}"
|
||||
ditto "${APP_BUNDLE}" "${INSTALL_PATH}" || fail "Could not install ${APP_BUNDLE_NAME} in ${INSTALL_DIR}. Check permissions and retry."
|
||||
|
||||
INSTALLED_EXECUTABLE="${INSTALL_PATH}/Contents/MacOS/${EXECUTABLE_NAME}"
|
||||
[[ -x "${INSTALLED_EXECUTABLE}" ]] || fail "Installed executable is missing at ${INSTALLED_EXECUTABLE}."
|
||||
SOURCE_HASH="$(shasum -a 256 "${MACOS_DIR}/${EXECUTABLE_NAME}" | awk '{print $1}')"
|
||||
INSTALLED_HASH="$(shasum -a 256 "${INSTALLED_EXECUTABLE}" | awk '{print $1}')"
|
||||
[[ "${SOURCE_HASH}" == "${INSTALLED_HASH}" ]] || fail "Installed executable does not match the newly built executable."
|
||||
|
||||
step "Launching ${APP_DISPLAY_NAME}"
|
||||
open -n "${INSTALL_PATH}" || fail "${APP_DISPLAY_NAME} was installed but could not be opened."
|
||||
|
||||
print -- "==> ${APP_DISPLAY_NAME} was rebuilt, installed, and launched successfully."
|
||||
Reference in New Issue
Block a user