#!/usr/bin/env bash set -euo pipefail # Prole build script — builds a macOS .app bundle without launching Xcode # Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo, xcodebuild) APP_NAME="Prole" BUNDLE_ID="org.prole.${APP_NAME}" MIN_MACOS="12.0" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$SCRIPT_DIR" SRC_DIR="$ROOT_DIR/Sources" BUILD_DIR="$ROOT_DIR/.build-cli" DIST_DIR="$ROOT_DIR/dist" APP_DIR="$DIST_DIR/${APP_NAME}.app" CONTENTS_DIR="$APP_DIR/Contents" MACOS_DIR="$CONTENTS_DIR/MacOS" RESOURCES_DIR="$CONTENTS_DIR/Resources" INFO_PLIST="$CONTENTS_DIR/Info.plist" PARENT_DIR="$(cd "$ROOT_DIR/.." && pwd)" LOG_DIR="$BUILD_DIR/logs" ARCH_CURRENT="$(uname -m)" # arm64 or x86_64 # Mandatory dependency sources (auto-fetched) URL_ROYALVNC="https://github.com/royalapplications/royalvnc/archive/refs/tags/1.0.1.tar.gz" # Deps staging directories DEPS_DIR="$BUILD_DIR/deps" DEPS_SRC_DIR="$DEPS_DIR/src" DEPS_OUT_DIR="$DEPS_DIR/out" DEPS_LOGS_DIR="$DEPS_OUT_DIR/logs" usage() { cat < Build for a specific arch (arm64 or x86_64). Defaults to host arch. --verbose Print verbose build commands (same as PROLE_VERBOSE=1) Examples: ./build.sh build ./build.sh build --arch arm64 ./build.sh build-universal ./build.sh run ./build.sh package Dependencies: IRC and RoyalVNC are built via Swift Package Manager. Environment overrides (optional): ROYALVNCKIT_XCFRAMEWORK If set, use this RoyalVNCKit.xcframework instead of building KEEP_DEPS=1 Keep the deps staging directory after the build (for debugging) PROLE_VERBOSE=1 Stream verbose output from SwiftPM/xcodebuild and echo commands EOF } ensure_dirs() { mkdir -p "$BUILD_DIR" "$DIST_DIR" "$MACOS_DIR" "$RESOURCES_DIR" "$CONTENTS_DIR/Frameworks" "$DEPS_SRC_DIR" "$DEPS_OUT_DIR" "$LOG_DIR" "$DEPS_LOGS_DIR" } # Determine verbosity from env/flags VERBOSE="${PROLE_VERBOSE:-0}" if [[ "$VERBOSE" = "1" ]]; then # Enable shell tracing for more insight set -x fi # Resolve a SwiftPM-capable command (prefer xcrun swift on macOS) resolve_swiftpm() { if command -v xcrun >/dev/null 2>&1 && xcrun swift --version >/dev/null 2>&1; then echo "xcrun swift" return 0 fi if command -v swift >/dev/null 2>&1; then if swift build --help >/dev/null 2>&1; then echo "swift" return 0 fi fi # Fallback to package form via xcrun if available if command -v xcrun >/dev/null 2>&1 && xcrun swift package --help >/dev/null 2>&1; then echo "xcrun swift" return 0 fi return 1 } SWIFTPM_CMD=$(resolve_swiftpm || true) if [[ -z "${SWIFTPM_CMD:-}" ]]; then echo "[spm] error: Swift Package Manager not found. Install Xcode Command Line Tools: xcode-select --install" >&2 xcodebuild -version 2>/dev/null || true swift --version 2>/dev/null || true exit 1 fi echo "[spm] Using SwiftPM: ${SWIFTPM_CMD}" # Read a key from prole.properties (very simple parser) prop_get() { local key="$1" local file="$ROOT_DIR/prole.properties" if [[ -f "$file" ]]; then local line line=$(grep -E "^${key}=" "$file" | tail -n1 || true) if [[ -n "$line" ]]; then echo "${line#*=}" return 0 fi fi return 1 } gen_statusbar_icon_png() { # Generates a small monochrome template PNG for the status bar (18x18) local out_png="$RESOURCES_DIR/statusIcon.png" # Render a simple 'P' using AppKit to avoid extra deps /usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$out_png" import AppKit import Foundation let args = CommandLine.arguments guard args.count > 1 else { exit(2) } let path = args[1] let size = NSSize(width: 18, height: 18) let img = NSImage(size: size) img.lockFocus() NSColor.clear.setFill() NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill() let paragraph = NSMutableParagraphStyle() paragraph.alignment = .center let attrs: [NSAttributedString.Key: Any] = [ .font: NSFont.monospacedSystemFont(ofSize: 14, weight: .bold), .foregroundColor: NSColor.labelColor, .paragraphStyle: paragraph ] let s = NSString(string: "P") let rect = NSRect(x: 0, y: -2, width: size.width, height: size.height) s.draw(in: rect, withAttributes: attrs) img.unlockFocus() guard let tiff = img.tiffRepresentation, let rep = NSBitmapImageRep(data: tiff), let png = rep.representation(using: .png, properties: [:]) else { exit(3) } try! png.write(to: URL(fileURLWithPath: path)) SWIFT # Mark as template via extended attribute for system tinting (optional) } gen_app_icns() { # Create an .icns for the app. Prefer a configured source image (ui.icon), # otherwise fall back to generating a bold 'P'. local icon_name="${APP_NAME}" local iconset_dir="$BUILD_DIR/${icon_name}.iconset" rm -rf "$iconset_dir" mkdir -p "$iconset_dir" local base_png="$BUILD_DIR/${icon_name}_1024.png" # Try configured ui.icon relative to repo root local ui_icon ui_icon="$(prop_get ui.icon || true)" if [[ -n "$ui_icon" && -f "$PARENT_DIR/$ui_icon" ]]; then # Use the provided icon image as base; if not already 1024x1024, sips will resize for each size cp "$PARENT_DIR/$ui_icon" "$base_png" else # Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow /usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png" import AppKit import Foundation let args = CommandLine.arguments guard args.count > 1 else { exit(2) } let outPath = args[1] let size = NSSize(width: 1024, height: 1024) let img = NSImage(size: size) img.lockFocus() NSColor.clear.setFill() NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill() // Draw a rounded rect background to make it look like an app icon let bgRect = NSRect(x: 0, y: 0, width: 1024, height: 1024) let radius: CGFloat = 220 let roundRectPath = NSBezierPath(roundedRect: bgRect, xRadius: radius, yRadius: radius) NSColor.windowBackgroundColor.setFill() roundRectPath.fill() // Draw the letter 'P' centered let paragraph = NSMutableParagraphStyle() paragraph.alignment = .center let attrs: [NSAttributedString.Key: Any] = [ .font: NSFont.monospacedSystemFont(ofSize: 720, weight: .black), .foregroundColor: NSColor.labelColor, .paragraphStyle: paragraph ] let s = NSString(string: "P") let rect = NSRect(x: 0, y: 70, width: 1024, height: 820) s.draw(in: rect, withAttributes: attrs) img.unlockFocus() guard let tiff = img.tiffRepresentation, let rep = NSBitmapImageRep(data: tiff), let png = rep.representation(using: .png, properties: [:]) else { exit(3) } try! png.write(to: URL(fileURLWithPath: outPath)) SWIFT fi # Create all iconset sizes from the base image for s in 16 32 128 256 512; do /usr/bin/sips -z "$s" "$s" "$base_png" --out "$iconset_dir/icon_${s}x${s}.png" >/dev/null /usr/bin/sips -z "$((s*2))" "$((s*2))" "$base_png" --out "$iconset_dir/icon_${s}x${s}@2x.png" >/dev/null done # Build icns /usr/bin/iconutil -c icns "$iconset_dir" -o "$RESOURCES_DIR/${icon_name}.icns" } gen_plist() { cat > "$INFO_PLIST" < CFBundleDevelopmentRegionen CFBundleExecutable${APP_NAME} CFBundleIdentifier${BUNDLE_ID} CFBundleInfoDictionaryVersion6.0 CFBundleName${APP_NAME} CFBundlePackageTypeAPPL CFBundleShortVersionString1.0 CFBundleVersion1 LSMinimumSystemVersion${MIN_MACOS} NSHighResolutionCapable NSPrincipalClassNSApplication LSApplicationCategoryTypepublic.app-category.developer-tools LSUIElement CFBundleIconFile${APP_NAME}.icns PLIST } copy_extra_resources() { # Copy default properties file if present local props_src="$ROOT_DIR/prole.properties" if [[ -f "$props_src" ]]; then cp "$props_src" "$RESOURCES_DIR/prole.properties" echo "[resources] Copied prole.properties into Resources" else echo "[resources] prole.properties not found at $props_src (skipping)" fi # Copy kubectl port-forward init script into Resources (consumed by app at runtime) local kpf_src="$ROOT_DIR/../etc/init-port-fowards.sh" if [[ -f "$kpf_src" ]]; then cp "$kpf_src" "$RESOURCES_DIR/init-port-fowards.sh" chmod +x "$RESOURCES_DIR/init-port-fowards.sh" || true echo "[resources] Copied init-port-fowards.sh into Resources" else echo "[resources] init-port-fowards.sh not found at $kpf_src (skipping)" fi } # --- Dependency preparation (RoyalVNCKit) --- have_cmd() { command -v "$1" >/dev/null 2>&1; } fetch_tarball() { local url="$1"; local out_tar="$2" echo "[deps] Fetching: $url" if have_cmd wget; then wget -q -O "$out_tar" "$url" else curl -LsSf -o "$out_tar" "$url" fi } extract_tarball() { local tarpath="$1"; local destdir="$2" mkdir -p "$destdir" tar -xzf "$tarpath" -C "$destdir" } # Run a command and tee stdout/stderr to a logfile, returning the command's exit status log_exec() { local logfile="$1"; shift mkdir -p "$(dirname "$logfile")" echo "[log] → $logfile" "$@" 2>&1 | tee "$logfile" return ${PIPESTATUS[0]} } xc_archive_one() { local proj_dir="$1"; local scheme="$2"; local arch="$3"; local outdir="$4" local archive_path="$outdir/${scheme}-macos-${arch}.xcarchive" echo "[xcodebuild] archive scheme=$scheme arch=$arch" local proj_opt=() # If an explicit Xcode project matching the scheme exists, use it (rare for SwiftPM) if [[ -d "$proj_dir/${scheme}.xcodeproj" ]]; then proj_opt=( -project "${scheme}.xcodeproj" ) fi local logfile="$LOG_DIR/xcode-archive-${scheme}-${arch}.log" if [[ "$scheme" == "RoyalVNCKit" ]]; then logfile="$DEPS_LOGS_DIR/royalvnc-archive-${arch}.log"; fi pushd "$proj_dir" >/dev/null || return 1 # Per-scheme extra flags / xcconfig local extra_args=() if [[ "$scheme" == "RoyalVNCKit" ]]; then # Enable distribution interfaces so SPM can import the Swift module, but disable verification to avoid toolchain issues local xcflags_file="$outdir/royalvnc-archive-overrides.xcconfig" cat > "$xcflags_file" <<'XCCONFIG' BUILD_LIBRARY_FOR_DISTRIBUTION = YES SWIFT_EMIT_MODULE_INTERFACE = YES SWIFT_SERIALIZE_DEBUGGING_OPTIONS = NO OTHER_SWIFT_FLAGS = $(inherited) -no-verify-emitted-module-interface XCCONFIG extra_args+=( -xcconfig "$xcflags_file" ) fi log_exec "$logfile" xcodebuild archive \ -scheme "$scheme" \ -destination 'generic/platform=macOS' \ -configuration Release \ -archivePath "$archive_path" \ -sdk macosx \ -disableAutomaticPackageResolution \ SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES ARCHS="$arch" ONLY_ACTIVE_ARCH=NO \ SWIFT_VERSION=5.0 \ SWIFT_STRICT_CONCURRENCY=minimal \ MACOSX_DEPLOYMENT_TARGET="${MIN_MACOS}" \ ${extra_args[@]:-} \ ${proj_opt[@]:-} \ || { popd >/dev/null; return 1; } popd >/dev/null } # Build one framework w/ xcodebuild (not archive), outputting a .framework into outdir xc_build_framework_one() { local proj_dir="$1"; local scheme="$2"; local arch="$3"; local outdir="$4"; local product_name="$5" local build_dir="$outdir/${scheme}-macos-${arch}-build" mkdir -p "$build_dir" local logfile="$DEPS_LOGS_DIR/${scheme}-build-${arch}.log" pushd "$proj_dir" >/dev/null || return 1 # Prepare xcconfig to disable interface emission/verification local xcflags_file="$outdir/${scheme}-build-overrides.${arch}.xcconfig" cat > "$xcflags_file" <<'XCCONFIG' // Emit module interfaces suitable for distribution so the Swift module can be imported by SPM BUILD_LIBRARY_FOR_DISTRIBUTION = YES SWIFT_EMIT_MODULE_INTERFACE = YES SWIFT_SERIALIZE_DEBUGGING_OPTIONS = NO // But do not verify emitted module interfaces to avoid toolchain-specific failures OTHER_SWIFT_FLAGS = $(inherited) -no-verify-emitted-module-interface XCCONFIG # Important: write all xcodebuild output to the log to keep stdout clean (we echo only the path below) xcodebuild build \ -scheme "$scheme" \ -destination 'generic/platform=macOS' \ -configuration Release \ -sdk macosx \ ARCHS="$arch" ONLY_ACTIVE_ARCH=NO \ SWIFT_VERSION=5.0 \ SWIFT_STRICT_CONCURRENCY=minimal \ MACOSX_DEPLOYMENT_TARGET="${MIN_MACOS}" \ CONFIGURATION_BUILD_DIR="$build_dir" \ -xcconfig "$xcflags_file" \ >"$logfile" 2>&1 || { popd >/dev/null; return 1; } popd >/dev/null # Accept both plain build dir and SwiftPM PackageFrameworks location if [[ -d "$build_dir/${product_name}.framework" ]]; then echo "$build_dir/${product_name}.framework" return 0 fi if [[ -d "$build_dir/PackageFrameworks/${product_name}.framework" ]]; then echo "$build_dir/PackageFrameworks/${product_name}.framework" return 0 fi return 1 } build_royalvnc_xcframework() { if [[ -n ${ROYALVNCKIT_XCFRAMEWORK:-} && -d ${ROYALVNCKIT_XCFRAMEWORK} ]]; then echo "[deps] Using provided RoyalVNCKit.xcframework: ${ROYALVNCKIT_XCFRAMEWORK}" return 0 fi local work="$DEPS_SRC_DIR/RoyalVNC-src" local tar="$DEPS_DIR/royalvnc.tar.gz" rm -rf "$work"; mkdir -p "$work" fetch_tarball "$URL_ROYALVNC" "$tar" extract_tarball "$tar" "$work" local src_root src_root="$(find "$work" -maxdepth 1 -type d -name 'royalvnc-*' | head -n1)" if [[ -z "$src_root" ]]; then echo "[deps] error: RoyalVNC source not found after extract" >&2 exit 1 fi pushd "$src_root" >/dev/null local build_tmp="$DEPS_OUT_DIR/RoyalVNC-build" rm -rf "$build_tmp"; mkdir -p "$build_tmp" local have_arm=0; local have_x86=0 local fw_arm=""; local fw_x86="" local want_arches=(arm64 x86_64) if [[ "${PROLE_BUILD_ARCH:-}" == "arm64" ]]; then want_arches=(arm64) elif [[ "${PROLE_BUILD_ARCH:-}" == "x86_64" ]]; then want_arches=(x86_64) fi for dep_arch in "${want_arches[@]}"; do if [[ "$dep_arch" == "arm64" ]]; then # Prefer archive to produce proper Swift module interfaces; fall back to build if archive fails if xc_archive_one "$PWD" RoyalVNCKit arm64 "$build_tmp"; then fw_arm="$build_tmp/RoyalVNCKit-macos-arm64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" have_arm=1 elif fw_arm=$(xc_build_framework_one "$PWD" RoyalVNCKit arm64 "$build_tmp" RoyalVNCKit); then have_arm=1 fi else if xc_archive_one "$PWD" RoyalVNCKit x86_64 "$build_tmp"; then fw_x86="$build_tmp/RoyalVNCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" have_x86=1 elif fw_x86=$(xc_build_framework_one "$PWD" RoyalVNCKit x86_64 "$build_tmp" RoyalVNCKit); then have_x86=1 fi fi done local out_xc="$DEPS_OUT_DIR/RoyalVNCKit.xcframework" rm -rf "$out_xc" if [[ $have_arm -eq 1 && $have_x86 -eq 1 ]]; then log_exec "$DEPS_LOGS_DIR/royalvnc-create-xcframework.log" xcodebuild -create-xcframework \ -framework "$fw_arm" \ -framework "$fw_x86" \ -output "$out_xc" elif [[ $have_arm -eq 1 ]]; then log_exec "$DEPS_LOGS_DIR/royalvnc-create-xcframework.log" xcodebuild -create-xcframework \ -framework "$fw_arm" \ -output "$out_xc" elif [[ $have_x86 -eq 1 ]]; then log_exec "$DEPS_LOGS_DIR/royalvnc-create-xcframework.log" xcodebuild -create-xcframework \ -framework "$fw_x86" \ -output "$out_xc" else echo "[deps] error: failed to build RoyalVNCKit for any macOS arch" >&2 exit 1 fi export ROYALVNCKIT_XCFRAMEWORK="$out_xc" echo "[deps] Built RoyalVNCKit.xcframework at $ROYALVNCKIT_XCFRAMEWORK" # Provide a stable path within the package for SwiftPM binaryTarget resolution local vendor_dir="$ROOT_DIR/Vendor" mkdir -p "$vendor_dir" rm -rf "$vendor_dir/RoyalVNCKit.xcframework" cp -R "$ROYALVNCKIT_XCFRAMEWORK" "$vendor_dir/" echo "[deps] Mirrored RoyalVNCKit.xcframework to $vendor_dir" popd >/dev/null } prepare_dependencies() { ensure_dirs echo "[deps] (deprecated) RoyalVNCKit manual build not required; managed by SwiftPM" } cleanup_dependencies() { if [[ "${KEEP_DEPS:-0}" = "1" ]]; then echo "[deps] KEEP_DEPS=1 set; preserving $DEPS_DIR" else rm -rf "$DEPS_DIR" echo "[deps] Cleaned deps staging directory" fi } # Build via Swift Package Manager and return path to built binary on stdout spm_build_binary() { local arch="$1" echo "[spm] Building (Release) for arch=${arch}" >&2 # ROOT_DIR already points to prole-app; build from there so Package.swift is visible. pushd "$ROOT_DIR" >/dev/null || return 1 mkdir -p "$LOG_DIR" # Build via SwiftPM (Package.swift in prole-app). All dependencies managed by SPM. # Prepare argument arrays to avoid word-splitting issues local spm_verbose_flag=() if [[ "$VERBOSE" = "1" ]]; then spm_verbose_flag=( -v ) fi local build_flags=( -c release --arch "$arch" \ -Xlinker -rpath -Xlinker "@executable_path/../Frameworks" ) # Turn SWIFTPM_CMD (e.g., "xcrun swift") into an array local -a SWIFTPM_ARR # shellcheck disable=SC2206 SWIFTPM_ARR=( $SWIFTPM_CMD ) # If verbose, stream output to stdout and tee to log; otherwise, write only to log if [[ "$VERBOSE" = "1" ]]; then echo "[spm] Executing: ${SWIFTPM_CMD} build ${spm_verbose_flag[*]} ${build_flags[*]}" "${SWIFTPM_ARR[@]}" build "${spm_verbose_flag[@]}" "${build_flags[@]}" 2>&1 | tee "$LOG_DIR/spm-build-${arch}.log" local rc=${PIPESTATUS[0]} if [[ $rc -ne 0 ]]; then popd >/dev/null; return $rc; fi else "${SWIFTPM_ARR[@]}" build "${build_flags[@]}" >"$LOG_DIR/spm-build-${arch}.log" 2>&1 || { popd >/dev/null; return 1; } fi popd >/dev/null local candidate1="$ROOT_DIR/.build/${arch}-apple-macosx/release/${APP_NAME}" local candidate2="$ROOT_DIR/.build/release/${APP_NAME}" if [[ -x "$candidate1" ]]; then echo "$candidate1"; return 0 fi if [[ -x "$candidate2" ]]; then echo "$candidate2"; return 0 fi return 1 } codesign_app() { echo "[codesign] Ad-hoc signing ${APP_DIR}" xcrun codesign --force --deep -s - "$APP_DIR" } # Copy SwiftPM-produced dynamic libraries (e.g., libRoyalVNCKit.dylib) into the app bundle embed_spm_dylibs() { local arch="$1" local spm_lib_dir="$ROOT_DIR/.build/${arch}-apple-macosx/release" if [[ ! -d "$spm_lib_dir" ]]; then spm_lib_dir="$ROOT_DIR/.build/release" fi mkdir -p "$CONTENTS_DIR/Frameworks" local found=0 if compgen -G "$spm_lib_dir/*.dylib" > /dev/null; then for dyl in "$spm_lib_dir"/*.dylib; do found=1 echo "[embed] Copying $(basename "$dyl") into Frameworks" cp -f "$dyl" "$CONTENTS_DIR/Frameworks/" done fi if [[ $found -eq 1 ]]; then echo "[embed] Codesigning embedded dylibs" # Sign all copied dylibs find "$CONTENTS_DIR/Frameworks" -name "*.dylib" -print0 | while IFS= read -r -d '' f; do xcrun codesign --force -s - "$f" done fi } build_one_arch() { local arch="$1" # Default clean at the start of every build clean || true # Constrain dependency builds to the requested architecture to avoid toolchain incompatibilities export PROLE_BUILD_ARCH="$arch" prepare_dependencies ensure_dirs gen_plist gen_app_icns gen_statusbar_icon_png copy_extra_resources local built_bin built_bin=$(spm_build_binary "$arch" | tail -n1) || { echo "[spm] build failed" >&2; exit 1; } mkdir -p "$MACOS_DIR" cp "$built_bin" "$MACOS_DIR/${APP_NAME}" # Embed any SwiftPM dynamic libs (e.g., RoyalVNCKit) into the app bundle embed_spm_dylibs "$arch" # Ensure the app binary can locate embedded dylibs in Contents/Frameworks at runtime echo "[rpath] Adding @executable_path/../Frameworks to app binary rpaths" xcrun install_name_tool -add_rpath "@executable_path/../Frameworks" "$MACOS_DIR/${APP_NAME}" 2>/dev/null || true codesign_app echo "Built: $APP_DIR" echo "[summary] App binary: $(lipo -info "$MACOS_DIR/${APP_NAME}" 2>/dev/null || echo unknown)" echo "[summary] Embedded frameworks (.framework):" /usr/bin/find "$CONTENTS_DIR/Frameworks" -maxdepth 1 -type d -name "*.framework" -exec basename {} \; | sed 's/^/ - /' echo "[summary] Embedded dynamic libraries (.dylib):" /usr/bin/find "$CONTENTS_DIR/Frameworks" -maxdepth 1 -type f -name "*.dylib" -exec basename {} \; | sed 's/^/ - /' cleanup_dependencies } build_universal() { # Default clean at the start of every universal build clean || true # Build dependencies for both arches in universal build unset PROLE_BUILD_ARCH || true prepare_dependencies ensure_dirs gen_plist local built_arm64; built_arm64=$(spm_build_binary arm64 | tail -n1) || { echo "[spm] arm64 build failed" >&2; exit 1; } local built_x86; built_x86=$(spm_build_binary x86_64 | tail -n1) || { echo "[spm] x86_64 build failed" >&2; exit 1; } mkdir -p "$MACOS_DIR" xcrun lipo -create -output "$MACOS_DIR/${APP_NAME}" "$built_arm64" "$built_x86" codesign_app echo "Built universal: $APP_DIR" echo "[summary] App binary: $(lipo -info "$MACOS_DIR/${APP_NAME}" 2>/dev/null || echo unknown)" echo "[summary] Embedded frameworks (.framework):" /usr/bin/find "$CONTENTS_DIR/Frameworks" -maxdepth 1 -type d -name "*.framework" -exec basename {} \; | sed 's/^/ - /' echo "[summary] Embedded dynamic libraries (.dylib):" /usr/bin/find "$CONTENTS_DIR/Frameworks" -maxdepth 1 -type f -name "*.dylib" -exec basename {} \; | sed 's/^/ - /' cleanup_dependencies } clean() { rm -rf "$BUILD_DIR" "$DIST_DIR" echo "Cleaned build artifacts." } run_app() { if [ ! -d "$APP_DIR" ]; then "$0" build fi echo "[run] Launching ${APP_NAME}.app" # Launch app in background without activating it; return immediately. # Then confirm it is running and print its PID. open -gn "$APP_DIR" # Wait briefly for the app to start and obtain PID for i in {1..20}; do PID=$(pgrep -x "$APP_NAME" || true) if [ -n "${PID:-}" ]; then echo "[run] ${APP_NAME} is running (pid: $PID)." return 0 fi sleep 0.1 done echo "[run] Warning: could not confirm ${APP_NAME} is running. Check Console/Activity Monitor." >&2 } package_app() { if [ ! -d "$APP_DIR" ]; then "$0" build fi local zip="$DIST_DIR/${APP_NAME}.zip" (cd "$DIST_DIR" && /usr/bin/zip -qry "$(basename "$zip")" "$(basename "$APP_DIR")") echo "Packaged: $zip" } cmd="${1:-build}" shift || true case "$cmd" in -h|--help|help) usage ;; clean) clean ;; build) arch="${ARCH_CURRENT}" # Allow per-invocation verbose flag while [ $# -gt 0 ]; do case "$1" in --arch) arch="$2"; shift 2 ;; --verbose) VERBOSE="1"; shift ;; *) echo "Unknown option: $1"; usage; exit 1 ;; esac done build_one_arch "$arch" ;; build-universal) build_universal ;; run) run_app ;; debug) # Build if needed, then run the binary directly in the foreground if [ ! -d "$APP_DIR" ]; then "$0" build fi BIN="$MACOS_DIR/${APP_NAME}" if [ ! -x "$BIN" ]; then echo "[debug] error: binary not found at $BIN" >&2 exit 1 fi echo "[debug] Running ${APP_NAME} in foreground with verbose logs" echo "[debug] Press Ctrl+C to terminate. To quit from UI, use the context menu or Cmd+Q." export PROLESTATUS_DEBUG=1 "$BIN" status=$? echo "[debug] ${APP_NAME} exited with status ${status}" exit $status ;; package) package_app ;; *) echo "Unknown command: $cmd"; usage; exit 1 ;; esac