314 lines
8.8 KiB
Bash
314 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# macOS Production Build Script for the SDL3 Tetris project
|
|
# Mirrors the Windows PowerShell workflow but uses common POSIX tooling so it
|
|
# can be executed on macOS runners or local developer machines.
|
|
|
|
PROJECT_NAME="tetris"
|
|
BUILD_DIR="build-release"
|
|
OUTPUT_DIR="dist"
|
|
PACKAGE_DIR=""
|
|
VERSION="$(date +"%Y.%m.%d")"
|
|
CLEAN=0
|
|
PACKAGE_ONLY=0
|
|
PACKAGE_RUNTIME_DIR=""
|
|
|
|
print_usage() {
|
|
cat <<'USAGE'
|
|
Usage: ./build-production-mac.sh [options]
|
|
|
|
Options:
|
|
-c, --clean Remove existing build + dist folders before running
|
|
-p, --package-only Skip compilation and only rebuild the distributable
|
|
-o, --output DIR Customize output directory (default: dist)
|
|
-h, --help Show this help text
|
|
USAGE
|
|
}
|
|
|
|
log() {
|
|
local level=$1; shift
|
|
case "$level" in
|
|
INFO) printf '\033[36m[INFO]\033[0m %s\n' "$*" ;;
|
|
OK) printf '\033[32m[ OK ]\033[0m %s\n' "$*" ;;
|
|
WARN) printf '\033[33m[WARN]\033[0m %s\n' "$*" ;;
|
|
ERR) printf '\033[31m[ERR ]\033[0m %s\n' "$*" ;;
|
|
*) printf '%s\n' "$*" ;;
|
|
esac
|
|
}
|
|
|
|
require_macos() {
|
|
if [[ $(uname) != "Darwin" ]]; then
|
|
log ERR "This script is intended for macOS hosts."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-c|--clean) CLEAN=1; shift ;;
|
|
-p|--package-only) PACKAGE_ONLY=1; shift ;;
|
|
-o|--output)
|
|
if [[ $# -lt 2 ]]; then
|
|
log ERR "--output requires a directory argument"
|
|
exit 1
|
|
fi
|
|
OUTPUT_DIR="$2"; shift 2 ;;
|
|
-h|--help) print_usage; exit 0 ;;
|
|
*)
|
|
log ERR "Unknown argument: $1"
|
|
print_usage
|
|
exit 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
configure_paths() {
|
|
PACKAGE_DIR="${OUTPUT_DIR}/TetrisGame-mac"
|
|
}
|
|
|
|
clean_previous() {
|
|
if (( CLEAN )); then
|
|
log INFO "Cleaning previous build artifacts..."
|
|
rm -rf "$BUILD_DIR" "$OUTPUT_DIR"
|
|
log OK "Previous artifacts removed"
|
|
fi
|
|
}
|
|
|
|
configure_and_build() {
|
|
if (( PACKAGE_ONLY )); then
|
|
return
|
|
fi
|
|
|
|
log INFO "Configuring CMake (Release)..."
|
|
cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release
|
|
log OK "CMake configure complete"
|
|
|
|
log INFO "Building Release target..."
|
|
cmake --build "$BUILD_DIR" --config Release
|
|
log OK "Build finished"
|
|
}
|
|
|
|
resolve_executable() {
|
|
local candidates=(
|
|
"$BUILD_DIR/Release/${PROJECT_NAME}"
|
|
"$BUILD_DIR/${PROJECT_NAME}"
|
|
"$BUILD_DIR/${PROJECT_NAME}.app/Contents/MacOS/${PROJECT_NAME}"
|
|
)
|
|
|
|
for path in "${candidates[@]}"; do
|
|
if [[ -x "$path" ]]; then
|
|
EXECUTABLE_PATH="$path"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -z ${EXECUTABLE_PATH:-} ]]; then
|
|
log ERR "Unable to locate built executable."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$EXECUTABLE_PATH" == *.app/Contents/MacOS/* ]]; then
|
|
APP_BUNDLE_PATH="${EXECUTABLE_PATH%/Contents/MacOS/*}"
|
|
else
|
|
APP_BUNDLE_PATH=""
|
|
fi
|
|
|
|
log OK "Using executable: $EXECUTABLE_PATH"
|
|
}
|
|
|
|
prepare_package_dir() {
|
|
log INFO "Creating package directory $PACKAGE_DIR ..."
|
|
rm -rf "$PACKAGE_DIR"
|
|
mkdir -p "$PACKAGE_DIR"
|
|
}
|
|
|
|
copy_binary_or_bundle() {
|
|
if [[ -n ${APP_BUNDLE_PATH:-} ]]; then
|
|
log INFO "Copying app bundle..."
|
|
rsync -a "$APP_BUNDLE_PATH" "$PACKAGE_DIR/"
|
|
local app_name="${APP_BUNDLE_PATH##*/}"
|
|
PACKAGE_BINARY="${app_name}/Contents/MacOS/${PROJECT_NAME}"
|
|
PACKAGE_RUNTIME_DIR="$PACKAGE_DIR/${app_name}/Contents/MacOS"
|
|
else
|
|
log INFO "Copying executable..."
|
|
cp "$EXECUTABLE_PATH" "$PACKAGE_DIR/${PROJECT_NAME}"
|
|
chmod +x "$PACKAGE_DIR/${PROJECT_NAME}"
|
|
PACKAGE_BINARY="${PROJECT_NAME}"
|
|
PACKAGE_RUNTIME_DIR="$PACKAGE_DIR"
|
|
fi
|
|
log OK "Binary ready (${PACKAGE_BINARY})"
|
|
}
|
|
|
|
copy_assets() {
|
|
if [[ -n ${APP_BUNDLE_PATH:-} ]]; then
|
|
log INFO "Assets already bundled inside the .app; skipping external copy."
|
|
return
|
|
fi
|
|
log INFO "Copying assets..."
|
|
local folders=("assets" "fonts")
|
|
for folder in "${folders[@]}"; do
|
|
if [[ -d "$folder" ]]; then
|
|
rsync -a "$folder" "$PACKAGE_DIR/"
|
|
log OK "Copied $folder"
|
|
fi
|
|
done
|
|
if [[ -f "FreeSans.ttf" ]]; then
|
|
cp "FreeSans.ttf" "$PACKAGE_DIR/"
|
|
log OK "Copied FreeSans.ttf"
|
|
fi
|
|
}
|
|
|
|
copy_dependencies() {
|
|
log INFO "Collecting dynamic libraries from vcpkg..."
|
|
local triplets=("arm64-osx" "x64-osx" "universal-osx")
|
|
local bases=("$BUILD_DIR/vcpkg_installed" "vcpkg_installed")
|
|
local copied_names=()
|
|
|
|
for base in "${bases[@]}"; do
|
|
for triplet in "${triplets[@]}"; do
|
|
for sub in lib bin; do
|
|
local dir="$base/$triplet/$sub"
|
|
if [[ -d "$dir" ]]; then
|
|
while IFS= read -r -d '' dylib; do
|
|
local name=$(basename "$dylib")
|
|
local seen=0
|
|
for existing in "${copied_names[@]}"; do
|
|
if [[ "$existing" == "$name" ]]; then
|
|
seen=1
|
|
break
|
|
fi
|
|
done
|
|
if (( !seen )); then
|
|
cp "$dylib" "$PACKAGE_RUNTIME_DIR/"
|
|
copied_names+=("$name")
|
|
log OK "Copied $name"
|
|
fi
|
|
done < <(find "$dir" -maxdepth 1 -type f -name '*.dylib' -print0)
|
|
fi
|
|
done
|
|
done
|
|
done
|
|
|
|
if [[ ${#copied_names[@]} -eq 0 ]]; then
|
|
log WARN "No .dylib files found; ensure vcpkg installed macOS triplet dependencies."
|
|
fi
|
|
}
|
|
|
|
ensure_rpath() {
|
|
local binary="$PACKAGE_DIR/$PACKAGE_BINARY"
|
|
if [[ ! -f "$binary" ]]; then
|
|
return
|
|
fi
|
|
if command -v otool >/dev/null 2>&1 && command -v install_name_tool >/dev/null 2>&1; then
|
|
if ! otool -l "$binary" | grep -A2 LC_RPATH | grep -q '@executable_path'; then
|
|
log INFO "Adding @executable_path rpath"
|
|
install_name_tool -add_rpath "@executable_path" "$binary"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
create_launchers() {
|
|
local launch_command="./${PROJECT_NAME}"
|
|
if [[ "$PACKAGE_BINARY" == *.app/Contents/MacOS/* ]]; then
|
|
local app_dir="${PACKAGE_BINARY%%/Contents/*}"
|
|
launch_command="open \"./${app_dir}\""
|
|
fi
|
|
|
|
cat > "$PACKAGE_DIR/Launch-Tetris.command" <<EOF
|
|
#!/usr/bin/env bash
|
|
cd "\$(dirname \"\$0\")"
|
|
${launch_command}
|
|
EOF
|
|
chmod +x "$PACKAGE_DIR/Launch-Tetris.command"
|
|
|
|
cat > "$PACKAGE_DIR/README-mac.txt" <<EOF
|
|
Tetris SDL3 Game - macOS Release $VERSION
|
|
=========================================
|
|
|
|
Requirements:
|
|
- macOS 12 Monterey or newer (Apple Silicon or Intel)
|
|
- GPU with Metal support
|
|
|
|
Installation:
|
|
1. Unzip the archive anywhere (e.g., ~/Games/Tetris).
|
|
2. Ensure the executable bit stays set (script already does this).
|
|
3. Double-click Launch-Tetris.command or run the binary/app manually from Terminal or Finder.
|
|
|
|
Files:
|
|
- tetris / Launch-Tetris.command (start the game)
|
|
- assets/ (art, audio, fonts)
|
|
- *.dylib from SDL3 and related dependencies
|
|
- FreeSans.ttf font
|
|
|
|
Enjoy!
|
|
EOF
|
|
log OK "Created README and launcher"
|
|
}
|
|
|
|
validate_package() {
|
|
log INFO "Validating package contents..."
|
|
local missing=()
|
|
local required=("$PACKAGE_BINARY")
|
|
if [[ -z ${APP_BUNDLE_PATH:-} ]]; then
|
|
required+=(assets FreeSans.ttf)
|
|
fi
|
|
for required in "${required[@]}"; do
|
|
if [[ ! -e "$PACKAGE_DIR/$required" ]]; then
|
|
missing+=("$required")
|
|
fi
|
|
done
|
|
if (( ${#missing[@]} > 0 )); then
|
|
log WARN "Missing: ${missing[*]}"
|
|
else
|
|
log OK "Package looks complete"
|
|
fi
|
|
}
|
|
|
|
create_zip() {
|
|
mkdir -p "$OUTPUT_DIR"
|
|
local zip_name="TetrisGame-mac-${VERSION}.zip"
|
|
local zip_path="$OUTPUT_DIR/$zip_name"
|
|
log INFO "Creating zip archive $zip_path ..."
|
|
if command -v ditto >/dev/null 2>&1; then
|
|
ditto -c -k --keepParent "$PACKAGE_DIR" "$zip_path"
|
|
else
|
|
(cd "$OUTPUT_DIR" && zip -r "$zip_name" "$(basename "$PACKAGE_DIR")")
|
|
fi
|
|
log OK "Zip created"
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
require_macos
|
|
configure_paths
|
|
|
|
if [[ ! -f "CMakeLists.txt" ]]; then
|
|
log ERR "Run from repository root (CMakeLists.txt missing)."
|
|
exit 1
|
|
fi
|
|
|
|
log INFO "======================================"
|
|
log INFO " macOS Production Builder"
|
|
log INFO "======================================"
|
|
log INFO "Version: $VERSION"
|
|
log INFO "Output: $OUTPUT_DIR"
|
|
|
|
clean_previous
|
|
configure_and_build
|
|
resolve_executable
|
|
prepare_package_dir
|
|
copy_binary_or_bundle
|
|
copy_assets
|
|
copy_dependencies
|
|
ensure_rpath
|
|
create_launchers
|
|
validate_package
|
|
create_zip
|
|
|
|
log INFO "Done. Package available at $PACKAGE_DIR"
|
|
}
|
|
|
|
main "$@"
|