326 lines
11 KiB
Markdown
326 lines
11 KiB
Markdown
# Build Files Template for music-assistant
|
|
|
|
Use this document as the prompt/instruction file for an agent when you create the
|
|
`music-assistant` Swift macOS app project.
|
|
|
|
The target project does not need to match the Servery path. These files should be
|
|
created in the root of the future `music-assistant` project, next to
|
|
`Package.swift`.
|
|
|
|
## Agent Request
|
|
|
|
Copy this request into the agent working inside the new project:
|
|
|
|
```text
|
|
Create the same build-command structure for this Swift macOS app.
|
|
|
|
App name:
|
|
music-assistant
|
|
|
|
Product name:
|
|
music-assistant
|
|
|
|
Bundle identifier:
|
|
com.music-assistant.app
|
|
|
|
Requirements:
|
|
1. Create `Makefile` with `build`, `dev`, and `test` commands.
|
|
2. Create `clean_rebuild.sh` for a clean release build, packaging, signing,
|
|
installing into `/Applications`, and launching the app.
|
|
3. Create `dev_rebuild.sh` for a fast incremental debug build, packaging,
|
|
installing into `/Applications`, and launching the app.
|
|
4. Assume these files live in the project root next to `Package.swift`.
|
|
5. Use the app icon at `Resources/music-assistant.icns`.
|
|
6. If the app has additional resources, copy them into
|
|
`Contents/Resources` during packaging.
|
|
7. Make both shell scripts executable.
|
|
8. Keep the implementation small and local. Do not refactor unrelated project
|
|
files.
|
|
9. After creating the files, verify that the scripts reference the correct app
|
|
name, product name, bundle identifier, icon path, and install path.
|
|
```
|
|
|
|
## Makefile
|
|
|
|
Create this file as `Makefile`:
|
|
|
|
```makefile
|
|
.PHONY: build dev test
|
|
|
|
build:
|
|
./clean_rebuild.sh
|
|
|
|
dev:
|
|
./dev_rebuild.sh
|
|
|
|
test:
|
|
swift test
|
|
```
|
|
|
|
## dev_rebuild.sh
|
|
|
|
Create this file as `dev_rebuild.sh`:
|
|
|
|
```zsh
|
|
#!/bin/zsh
|
|
# Fast development rebuild: incremental DEBUG build, repackage, relaunch.
|
|
#
|
|
# This skips `swift package clean` and release-mode optimizations, so it reuses
|
|
# the incremental build cache and finishes quickly. Use it while iterating.
|
|
|
|
set -euo pipefail
|
|
|
|
PROJECT_ROOT="$(cd "$(dirname "$0")" && pwd)"
|
|
APP_NAME="music-assistant"
|
|
PRODUCT_NAME="music-assistant"
|
|
APP_BUNDLE_NAME="${APP_NAME}.app"
|
|
BUILD_DIR="${PROJECT_ROOT}/.build"
|
|
APP_BUNDLE="${BUILD_DIR}/${APP_BUNDLE_NAME}"
|
|
INSTALL_PATH="/Applications/${APP_BUNDLE_NAME}"
|
|
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
|
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
|
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
|
APP_ICON="${PROJECT_ROOT}/Resources/music-assistant.icns"
|
|
|
|
fail() {
|
|
print -u2 -- "Error: $1"
|
|
exit 1
|
|
}
|
|
|
|
step() {
|
|
print -- "==> $1"
|
|
}
|
|
|
|
[[ -f "${PROJECT_ROOT}/Package.swift" ]] || fail "Package.swift was not found in ${PROJECT_ROOT}."
|
|
[[ -f "${APP_ICON}" ]] || fail "App icon was not found at ${APP_ICON}."
|
|
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."
|
|
|
|
cd "${PROJECT_ROOT}"
|
|
|
|
step "Stopping any running ${APP_NAME} application"
|
|
if pgrep -x "${PRODUCT_NAME}" >/dev/null 2>&1; then
|
|
pkill -x "${PRODUCT_NAME}" || fail "Could not stop the running ${APP_NAME} application."
|
|
for _ in {1..20}; do
|
|
pgrep -x "${PRODUCT_NAME}" >/dev/null 2>&1 || break
|
|
sleep 0.1
|
|
done
|
|
pgrep -x "${PRODUCT_NAME}" >/dev/null 2>&1 && fail "${APP_NAME} is still running. Quit it and retry."
|
|
fi
|
|
|
|
step "Building ${PRODUCT_NAME} in debug mode (incremental)"
|
|
swift build || fail "Debug build failed. Review the Swift compiler diagnostics above."
|
|
BIN_PATH="$(swift build --show-bin-path 2>/dev/null | tail -n1)"
|
|
SOURCE_EXECUTABLE="${BIN_PATH}/${PRODUCT_NAME}"
|
|
[[ -x "${SOURCE_EXECUTABLE}" ]] || 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}/${PRODUCT_NAME}"
|
|
cp "${APP_ICON}" "${RESOURCES_DIR}/music-assistant.icns"
|
|
|
|
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>music-assistant</string>
|
|
<key>CFBundleExecutable</key>
|
|
<string>music-assistant</string>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>com.music-assistant.app</string>
|
|
<key>CFBundleInfoDictionaryVersion</key>
|
|
<string>6.0</string>
|
|
<key>CFBundleIconFile</key>
|
|
<string>music-assistant</string>
|
|
<key>CFBundleName</key>
|
|
<string>music-assistant</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."
|
|
[[ -x "${MACOS_DIR}/${PRODUCT_NAME}" ]] || fail "Packaged executable is missing."
|
|
|
|
step "Installing ${APP_BUNDLE_NAME} in /Applications"
|
|
rm -rf "${INSTALL_PATH}"
|
|
ditto "${APP_BUNDLE}" "${INSTALL_PATH}" || fail "Could not install ${APP_BUNDLE_NAME} in /Applications. Check permissions and retry."
|
|
|
|
step "Launching ${APP_NAME}"
|
|
open -n "${INSTALL_PATH}" || fail "${APP_NAME} was installed but could not be opened."
|
|
|
|
print -- "==> ${APP_NAME} was rebuilt (debug), installed, and launched successfully."
|
|
```
|
|
|
|
## clean_rebuild.sh
|
|
|
|
Create this file as `clean_rebuild.sh`:
|
|
|
|
```zsh
|
|
#!/bin/zsh
|
|
# Build, package, install, and launch the music-assistant macOS application.
|
|
|
|
set -euo pipefail
|
|
|
|
PROJECT_ROOT="$(cd "$(dirname "$0")" && pwd)"
|
|
APP_NAME="music-assistant"
|
|
PRODUCT_NAME="music-assistant"
|
|
APP_BUNDLE_NAME="${APP_NAME}.app"
|
|
BUILD_DIR="${PROJECT_ROOT}/.build"
|
|
APP_BUNDLE="${BUILD_DIR}/${APP_BUNDLE_NAME}"
|
|
INSTALL_PATH="/Applications/${APP_BUNDLE_NAME}"
|
|
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
|
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
|
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
|
APP_ICON="${PROJECT_ROOT}/Resources/music-assistant.icns"
|
|
|
|
fail() {
|
|
print -u2 -- "Error: $1"
|
|
exit 1
|
|
}
|
|
|
|
step() {
|
|
print -- "==> $1"
|
|
}
|
|
|
|
[[ -f "${PROJECT_ROOT}/Package.swift" ]] || fail "Package.swift was not found in ${PROJECT_ROOT}."
|
|
[[ -f "${APP_ICON}" ]] || fail "App icon was not found at ${APP_ICON}."
|
|
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."
|
|
|
|
cd "${PROJECT_ROOT}"
|
|
|
|
step "Stopping any running ${APP_NAME} application"
|
|
if pgrep -x "${PRODUCT_NAME}" >/dev/null 2>&1; then
|
|
pkill -x "${PRODUCT_NAME}" || fail "Could not stop the running ${APP_NAME} application."
|
|
for _ in {1..20}; do
|
|
pgrep -x "${PRODUCT_NAME}" >/dev/null 2>&1 || break
|
|
sleep 0.1
|
|
done
|
|
pgrep -x "${PRODUCT_NAME}" >/dev/null 2>&1 && fail "${APP_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 ${PRODUCT_NAME} in release mode"
|
|
BIN_PATH="$(swift build -c release --show-bin-path 2>/dev/null | tail -n1)"
|
|
SOURCE_EXECUTABLE="${BIN_PATH}/${PRODUCT_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}/${PRODUCT_NAME}"
|
|
cp "${APP_ICON}" "${RESOURCES_DIR}/music-assistant.icns"
|
|
|
|
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>music-assistant</string>
|
|
<key>CFBundleExecutable</key>
|
|
<string>music-assistant</string>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>com.music-assistant.app</string>
|
|
<key>CFBundleInfoDictionaryVersion</key>
|
|
<string>6.0</string>
|
|
<key>CFBundleIconFile</key>
|
|
<string>music-assistant</string>
|
|
<key>CFBundleName</key>
|
|
<string>music-assistant</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}/${PRODUCT_NAME}" ]] || fail "Packaged executable is missing."
|
|
|
|
step "Installing ${APP_BUNDLE_NAME} in /Applications"
|
|
ditto "${APP_BUNDLE}" "${INSTALL_PATH}" || fail "Could not install ${APP_BUNDLE_NAME} in /Applications. Check permissions and retry."
|
|
|
|
INSTALLED_EXECUTABLE="${INSTALL_PATH}/Contents/MacOS/${PRODUCT_NAME}"
|
|
[[ -x "${INSTALLED_EXECUTABLE}" ]] || fail "Installed executable is missing at ${INSTALLED_EXECUTABLE}."
|
|
SOURCE_HASH="$(shasum -a 256 "${MACOS_DIR}/${PRODUCT_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_NAME}"
|
|
open -n "${INSTALL_PATH}" || fail "${APP_NAME} was installed but could not be opened."
|
|
|
|
print -- "==> ${APP_NAME} was rebuilt, installed, and launched successfully."
|
|
```
|
|
|
|
## Required Terminal Step
|
|
|
|
After the files are created in the new project, run:
|
|
|
|
```bash
|
|
chmod +x clean_rebuild.sh dev_rebuild.sh
|
|
```
|
|
|
|
Then use:
|
|
|
|
```bash
|
|
make dev
|
|
make build
|
|
make test
|
|
```
|
|
|
|
## Notes
|
|
|
|
- The future project must have `Package.swift`.
|
|
- The future project must have an icon at `Resources/music-assistant.icns`.
|
|
- If the executable product name in `Package.swift` is different, change
|
|
`PRODUCT_NAME` in both scripts.
|
|
- If the app needs permissions, add the matching usage-description keys to the
|
|
generated `Info.plist`.
|