From de1a34b1c240c143d0b014ea77fef6295bf82953 Mon Sep 17 00:00:00 2001 From: chrisfu Date: Fri, 5 Dec 2025 14:22:58 -0800 Subject: [PATCH] =?UTF-8?q?feat(build):=20milestone=20alpha=20build=20?= =?UTF-8?q?=E2=80=94=20conditional=20deps;=20IRCKit=20and=20RoyalVNCKit=20?= =?UTF-8?q?disabled=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This marks a milestone alpha build of Prole. Key changes - build.sh now supports conditional third‑party frameworks via env flags: - PROLE_ENABLE_IRCKit (default: 0) - PROLE_ENABLE_RoyalVNCKit (default: 0) - When disabled, IRCKit/RoyalVNCKit are not fetched, linked, or embedded. - Swift compile receives convenience defines for conditional compilation: - -D PROLE_ENABLE_IRCKit or -D PROLE_ENABLE_IRCKit_DISABLED - -D PROLE_ENABLE_RoyalVNCKit or -D PROLE_ENABLE_RoyalVNCKit_DISABLED - Archive flow stabilized; optional SSL-less path for IRCKit remains available via PROLE_DISABLE_SSL=1. - App bundle generation unchanged (plist, icons, resources, codesign). Why - Restore a clean build on current toolchains and allow incremental re‑enablement of optional integrations. - Make the build resilient: dependencies can be toggled on CI or locally without modifying sources. Usage examples - Default (alpha baseline; both off): ./build.sh build --arch arm64 - Enable IRCKit only: PROLE_ENABLE_IRCKit=1 ./build.sh build --arch arm64 - Enable RoyalVNCKit only: PROLE_ENABLE_RoyalVNCKit=1 ./build.sh build --arch arm64 - Enable both: PROLE_ENABLE_IRCKit=1 PROLE_ENABLE_RoyalVNCKit=1 ./build.sh build --arch arm64 Notes - IRCKit SSL-less switch remains available if needed: PROLE_DISABLE_SSL=1 PROLE_ENABLE_IRCKit=1 ./build.sh build --arch arm64 - Current alpha defaults keep both IRCKit and RoyalVNCKit disabled. Build: 2025-12-05 14:21 local --- prole-app/Sources/AppDelegate.swift | 24 ++ prole-app/build.sh | 464 +++++++++++++++++++++++++++- 2 files changed, 485 insertions(+), 3 deletions(-) diff --git a/prole-app/Sources/AppDelegate.swift b/prole-app/Sources/AppDelegate.swift index 676e2b9..b7f9ec7 100644 --- a/prole-app/Sources/AppDelegate.swift +++ b/prole-app/Sources/AppDelegate.swift @@ -14,6 +14,8 @@ import AppKit final class AppDelegate: NSObject, NSApplicationDelegate { private var overlayWindowController: OverlayWindowController! private var mainWindowController: MainWindowController! + private var workstationWindowController: WorkstationWindowController! + private var ircWindowController: IRCWindowController! private var hotKeyManager: HotKeyManager! private var serviceChecker: ServiceChecker! private var statusItemController: StatusItemController! @@ -65,6 +67,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { ), pfManager: pfManager) dlog("MainWindowController created") + // Workstation window (VNC viewer) — shown to the right of Prole Status when not in status bar mode + workstationWindowController = WorkstationWindowController() + dlog("WorkstationWindowController created") + + // IRC window — short vertically, long horizontally, positioned below Prole Status + ircWindowController = IRCWindowController() + dlog("IRCWindowController created") + // Create status bar item with icon and click handler // Status bar item with a monospaced "P" and its own right‑click menu statusItemController = StatusItemController(actions: .init( @@ -143,6 +153,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { private func switchToStatusBarMode() { mode = .statusBar mainWindowController.hide() + workstationWindowController.hide() + ircWindowController.hide() overlayWindowController.showOverlayNow() NSApp.setActivationPolicy(.accessory) statusItemController.setState(statusBarVisible: statusItemController.isVisible, appWindowVisible: false) @@ -154,6 +166,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { mode = .appWindow overlayWindowController.hideOverlay() mainWindowController.show() + // Position workstation window to the right of the main window and show it + if let ref = mainWindowController.window { workstationWindowController.position(rightOf: ref) } + workstationWindowController.show() + // Position IRC window below the main window and show it + if let ref = mainWindowController.window { ircWindowController.position(below: ref) } + ircWindowController.show() NSApp.setActivationPolicy(.regular) statusItemController.setState(statusBarVisible: statusItemController.isVisible, appWindowVisible: true) dlog("Switched to Application Window mode") @@ -173,9 +191,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate { private func toggleAppWindowVisibility() { if mainWindowController.isVisible { mainWindowController.hide() + workstationWindowController.hide() + ircWindowController.hide() if mode == .appWindow { mode = .statusBar } } else { mainWindowController.show() + if let ref = mainWindowController.window { workstationWindowController.position(rightOf: ref) } + workstationWindowController.show() + if let ref = mainWindowController.window { ircWindowController.position(below: ref) } + ircWindowController.show() mode = .appWindow overlayWindowController.hideOverlay() } diff --git a/prole-app/build.sh b/prole-app/build.sh index 6235aac..df066e5 100755 --- a/prole-app/build.sh +++ b/prole-app/build.sh @@ -2,7 +2,7 @@ set -euo pipefail # Prole build script — builds a macOS .app bundle without launching Xcode -# Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo) +# Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo, xcodebuild) APP_NAME="Prole" BUNDLE_ID="org.prole.${APP_NAME}" @@ -19,9 +19,20 @@ 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_IRCKIT="https://github.com/FuelRats/IRCKit/archive/refs/tags/0.16.0.tar.gz" +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 </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" == "IRCKit" ]]; then logfile="$DEPS_LOGS_DIR/irckit-archive-${arch}.log"; fi + 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" == "IRCKit" && -n ${PROLE_DISABLE_SSL:-} ]]; then + echo "[deps] PROLE_DISABLE_SSL=1: building IRCKit with PROLE_DISABLE_SSL compilation condition" + # Use a temporary xcconfig to avoid shell/argument parsing issues on the command line + local xcflags_file="$outdir/irckit-prole-disable-ssl.xcconfig" + # IMPORTANT: Keep this file strictly to valid xcconfig key/value lines (no comments or extra text) + cat > "$xcflags_file" <<'XCCONFIG' +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) PROLE_DISABLE_SSL +OTHER_SWIFT_FLAGS = $(inherited) -DPROLE_DISABLE_SSL +SWIFT_VERSION = 5.0 +SWIFT_STRICT_CONCURRENCY = minimal +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_irckit_xcframework() { + # If external override exists, trust it + if [[ -n ${IRCKIT_XCFRAMEWORK:-} && -d ${IRCKIT_XCFRAMEWORK} ]]; then + echo "[deps] Using provided IRCKit.xcframework: ${IRCKIT_XCFRAMEWORK}" + return 0 + fi + + local work="$DEPS_SRC_DIR/IRCKit-src" + local tar="$DEPS_DIR/irckit.tar.gz" + rm -rf "$work"; mkdir -p "$work" + fetch_tarball "$URL_IRCKIT" "$tar" + extract_tarball "$tar" "$work" + # Detect package or project root robustly + local src_root="" + # 1) If a Package.swift exists directly under work + if [[ -f "$work/Package.swift" ]]; then + src_root="$work" + fi + # 2) Else pick the first child directory that contains a Package.swift + if [[ -z "$src_root" ]]; then + for d in "$work"/*; do + [[ -d "$d" ]] || continue + if [[ -f "$d/Package.swift" ]]; then src_root="$d"; break; fi + done + fi + # 3) Else fall back to the first child directory (common for GitHub tarballs) + if [[ -z "$src_root" ]]; then + for d in "$work"/*; do + [[ -d "$d" ]] || continue + src_root="$d"; break + done + fi + if [[ -z "$src_root" ]]; then + echo "[deps] error: IRCKit source not found after extract" >&2 + exit 1 + fi + echo "[deps] IRCKit source root: $src_root" + pushd "$src_root" >/dev/null + # Ensure SwiftPM uses Swift 5 toolchain mode (avoid Swift 6 defaults under Xcode 16.x) + if have_cmd swift; then + echo "[deps] Forcing SwiftPM tools-version to 5.9 for IRCKit" + log_exec "$DEPS_LOGS_DIR/irckit-spm-tools-version.log" swift package tools-version --set 5.9 || true + fi + # Under SSL-less builds, strip NIOSSL from the graph and pin swift-nio to a Swift‑5–compatible release + if [[ -n ${PROLE_DISABLE_SSL:-} && -f "Package.swift" ]]; then + echo "[deps] PROLE_DISABLE_SSL=1: patching IRCKit Package.swift to remove NIOSSL and pin swift-nio to 2.40.0 (exact)" + cp -f "Package.swift" "$DEPS_LOGS_DIR/irckit-Package.swift.before" 2>/dev/null || true + # 1) Remove swift-nio-ssl package declaration lines entirely + perl -0777 -pe 's|\s*\.package\(\s*url:\s*"https://github\.com/apple/swift-nio-ssl"\s*,\s*[^\)]*\),?\n||g' -i "Package.swift" 2>/dev/null || true + # 2) Remove NIOSSL from any target dependency arrays (handles product and target name forms) + perl -0777 -pe 's|("|\[)NIOSSL(\]|"),?\s*||g; s|\[\s*,\s*\]|[]|g' -i "Package.swift" 2>/dev/null || true + perl -0777 -pe 's|\s*\.product\(name:\s*"NIOSSL"[^\)]*\),?\n||g' -i "Package.swift" 2>/dev/null || true + # 3) Pin swift-nio to an exact, Swift‑5–compatible version to avoid Swift‑6‑only codepaths + perl -0777 -pe 's|\.package\(\s*url:\s*"https://github\.com/apple/swift-nio"\s*,\s*from:\s*"[^"]+"\s*\)|.package(url: "https://github.com/apple/swift-nio", .exact("2.40.0"))|g' -i "Package.swift" 2>/dev/null || true + perl -0777 -pe 's|\.package\(\s*url:\s*"https://github\.com/apple/swift-nio"\s*,\s*\.upToNextMajor\(from:\s*"[^"]+"\)\s*\)|.package(url: "https://github.com/apple/swift-nio", .exact("2.40.0"))|g' -i "Package.swift" 2>/dev/null || true + perl -0777 -pe 's|\.package\(\s*url:\s*"https://github\.com/apple/swift-nio"\s*,\s*\.exact\([^\)]*\)\s*\)|.package(url: "https://github.com/apple/swift-nio", .exact("2.40.0"))|g' -i "Package.swift" 2>/dev/null || true + # 3b) Optionally pin swift-atomics to a Swift‑5–compatible release to help NIOConcurrencyHelpers + if ! grep -q "swift-atomics" Package.swift 2>/dev/null; then + echo "[deps] PROLE_DISABLE_SSL=1: adding swift-atomics .exact(\"1.0.2\") to Package.swift to stabilize NIOConcurrencyHelpers" + perl -0777 -pe 's|(dependencies\s*:\s*\[)|$1\n .package(url: "https://github.com/apple/swift-atomics", .exact("1.0.2")),|s' -i "Package.swift" 2>/dev/null || true + else + perl -0777 -pe 's|\.package\(\s*url:\s*"https://github\.com/apple/swift-atomics"\s*,\s*from:\s*"[^"]+"\s*\)|.package(url: "https://github.com/apple/swift-atomics", .exact("1.0.2"))|g' -i "Package.swift" 2>/dev/null || true + perl -0777 -pe 's|\.package\(\s*url:\s*"https://github\.com/apple/swift-atomics"\s*,\s*\.upToNextMajor\(from:\s*"[^"]+"\)\s*\)|.package(url: "https://github.com/apple/swift-atomics", .exact("1.0.2"))|g' -i "Package.swift" 2>/dev/null || true + fi + # 4) Ensure the package explicitly targets Swift 5 language mode if not already present + if ! grep -q "swiftLanguageVersions" Package.swift 2>/dev/null; then + echo "[deps] PROLE_DISABLE_SSL=1: injecting swiftLanguageVersions = [.v5] into Package.swift" + # Try to insert after the targets array before the closing paren of Package initializer + perl -0777 -pe 's|(targets\s*:\s*\[[\s\S]*?\])\s*\)\s*$|$1,\n swiftLanguageVersions: [.v5]\n)\n|s' -i "Package.swift" 2>/dev/null || true + fi + cp -f "Package.swift" "$DEPS_LOGS_DIR/irckit-Package.swift.after" 2>/dev/null || true + fi + # If SSL is disabled, avoid updating to latest; just resolve with our pinned/stripped graph + if have_cmd swift; then + if [[ -n ${PROLE_DISABLE_SSL:-} ]]; then + echo "[deps] PROLE_DISABLE_SSL=1: resolving IRCKit SwiftPM dependencies (no update)" + log_exec "$DEPS_LOGS_DIR/irckit-spm-resolve.log" swift package resolve || true + else + echo "[deps] Resolving IRCKit SwiftPM dependencies as pinned (no update)" + log_exec "$DEPS_LOGS_DIR/irckit-spm-resolve.log" swift package resolve || true + fi + # Best-effort diagnostics of the resolved graph + log_exec "$DEPS_LOGS_DIR/irckit-spm-show-deps.log" swift package show-dependencies || true + if [[ -f .swiftpm/resolved/Package.resolved ]]; then + cp -f .swiftpm/resolved/Package.resolved "$DEPS_LOGS_DIR/irckit-Package.resolved" || true + fi + fi + + # Note: Avoid surgical source patches; rely on version pinning per user request + local build_tmp="$DEPS_OUT_DIR/IRCKit-build" + rm -rf "$build_tmp"; mkdir -p "$build_tmp" + local have_arm=0; local have_x86=0 + # Determine which architectures to build for dependencies + 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 + if xc_archive_one "$PWD" IRCKit arm64 "$build_tmp"; then have_arm=1; fi + else + if xc_archive_one "$PWD" IRCKit x86_64 "$build_tmp"; then have_x86=1; fi + fi + done + local out_xc="$DEPS_OUT_DIR/IRCKit.xcframework" + rm -rf "$out_xc" + if [[ $have_arm -eq 1 && $have_x86 -eq 1 ]]; then + log_exec "$DEPS_LOGS_DIR/irckit-create-xcframework.log" xcodebuild -create-xcframework \ + -framework "$build_tmp/IRCKit-macos-arm64.xcarchive/Products/Library/Frameworks/IRCKit.framework" \ + -framework "$build_tmp/IRCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/IRCKit.framework" \ + -output "$out_xc" + elif [[ $have_arm -eq 1 ]]; then + log_exec "$DEPS_LOGS_DIR/irckit-create-xcframework.log" xcodebuild -create-xcframework \ + -framework "$build_tmp/IRCKit-macos-arm64.xcarchive/Products/Library/Frameworks/IRCKit.framework" \ + -output "$out_xc" + elif [[ $have_x86 -eq 1 ]]; then + log_exec "$DEPS_LOGS_DIR/irckit-create-xcframework.log" xcodebuild -create-xcframework \ + -framework "$build_tmp/IRCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/IRCKit.framework" \ + -output "$out_xc" + else + echo "[deps] error: failed to archive IRCKit for any macOS arch" >&2 + exit 1 + fi + export IRCKIT_XCFRAMEWORK="$out_xc" + echo "[deps] Built IRCKit.xcframework at $IRCKIT_XCFRAMEWORK" + popd >/dev/null +} + +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 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 + if xc_archive_one "$PWD" RoyalVNCKit arm64 "$build_tmp"; then have_arm=1; fi + else + if xc_archive_one "$PWD" RoyalVNCKit x86_64 "$build_tmp"; 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 "$build_tmp/RoyalVNCKit-macos-arm64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \ + -framework "$build_tmp/RoyalVNCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \ + -output "$out_xc" + elif [[ $have_arm -eq 1 ]]; then + log_exec "$DEPS_LOGS_DIR/royalvnc-create-xcframework.log" xcodebuild -create-xcframework \ + -framework "$build_tmp/RoyalVNCKit-macos-arm64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \ + -output "$out_xc" + elif [[ $have_x86 -eq 1 ]]; then + log_exec "$DEPS_LOGS_DIR/royalvnc-create-xcframework.log" xcodebuild -create-xcframework \ + -framework "$build_tmp/RoyalVNCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \ + -output "$out_xc" + else + echo "[deps] error: failed to archive RoyalVNCKit for any macOS arch" >&2 + exit 1 + fi + export ROYALVNCKIT_XCFRAMEWORK="$out_xc" + echo "[deps] Built RoyalVNCKit.xcframework at $ROYALVNCKIT_XCFRAMEWORK" + popd >/dev/null +} + +prepare_dependencies() { + ensure_dirs + local want_irc="${PROLE_ENABLE_IRCKit:-0}" + local want_vnc="${PROLE_ENABLE_RoyalVNCKit:-0}" + echo "[deps] Preparing frameworks — IRCKit: ${want_irc} (0=off,1=on), RoyalVNCKit: ${want_vnc} (0=off,1=on)" + + if [[ "$want_irc" = "1" ]]; then + build_irckit_xcframework + if [[ ! -d "${IRCKIT_XCFRAMEWORK:-}" ]]; then + echo "[deps] error: IRCKit.xcframework is missing" >&2; exit 1; fi + else + echo "[deps] IRCKit disabled (PROLE_ENABLE_IRCKit=0); skipping build" + IRCKIT_XCFRAMEWORK="" + fi + + if [[ "$want_vnc" = "1" ]]; then + build_royalvnc_xcframework + if [[ ! -d "${ROYALVNCKIT_XCFRAMEWORK:-}" ]]; then + echo "[deps] error: RoyalVNCKit.xcframework is missing" >&2; exit 1; fi + else + echo "[deps] RoyalVNCKit disabled (PROLE_ENABLE_RoyalVNCKit=0); skipping build" + ROYALVNCKIT_XCFRAMEWORK="" + fi +} + +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 +} + swiftc_compile() { local arch="$1"; shift local out="$1"; shift @@ -242,14 +561,41 @@ swiftc_compile() { echo "[swiftc] Building for ${arch} -> ${out}" + # Optional linking of frameworks based on flags + local want_irc="${PROLE_ENABLE_IRCKit:-0}" + local want_vnc="${PROLE_ENABLE_RoyalVNCKit:-0}" + local irc_flags=() + local vnc_flags=() + local swift_cfg_flags=() + if [[ "$want_irc" = "1" && -n "${IRCKIT_XCFRAMEWORK:-}" && -d "${IRCKIT_XCFRAMEWORK}" ]]; then + irc_flags=( -F "${IRCKIT_XCFRAMEWORK}" -framework IRCKit -Xlinker -rpath -Xlinker "@executable_path/../Frameworks" ) + swift_cfg_flags+=( -D PROLE_ENABLE_IRCKit ) + echo "[swiftc] Linking IRCKit from ${IRCKIT_XCFRAMEWORK}" + else + echo "[swiftc] IRCKit disabled or not present; not linking" + # Define a convenience symbol for conditional compilation if app code uses it + swift_cfg_flags+=( -D PROLE_ENABLE_IRCKit_DISABLED ) + fi + if [[ "$want_vnc" = "1" && -n "${ROYALVNCKIT_XCFRAMEWORK:-}" && -d "${ROYALVNCKIT_XCFRAMEWORK}" ]]; then + vnc_flags=( -F "${ROYALVNCKIT_XCFRAMEWORK}" -framework RoyalVNCKit -Xlinker -rpath -Xlinker "@executable_path/../Frameworks" ) + swift_cfg_flags+=( -D PROLE_ENABLE_RoyalVNCKit ) + echo "[swiftc] Linking RoyalVNCKit from ${ROYALVNCKIT_XCFRAMEWORK}" + else + echo "[swiftc] RoyalVNCKit disabled or not present; not linking" + swift_cfg_flags+=( -D PROLE_ENABLE_RoyalVNCKit_DISABLED ) + fi + # shellcheck disable=SC2046 - xcrun swiftc \ + log_exec "$LOG_DIR/swiftc-${arch}.log" xcrun swiftc \ -target "$target" \ -O \ -sdk "$(xcrun --show-sdk-path --sdk macosx)" \ -framework AppKit \ -framework Carbon \ -framework Network \ + ${swift_cfg_flags[@]:-} \ + ${irc_flags[@]:-} \ + ${vnc_flags[@]:-} \ $(/usr/bin/find "$SRC_DIR" -name "*.swift" | sort) \ -o "$out" } @@ -259,8 +605,103 @@ codesign_app() { xcrun codesign --force --deep -s - "$APP_DIR" } +embed_irckit_framework() { + if [[ "${PROLE_ENABLE_IRCKit:-0}" != "1" ]]; then + return 0 + fi + # Embed IRCKit.xcframework slice into Contents/Frameworks if available + if [[ -z "${IRCKIT_XCFRAMEWORK:-}" ]]; then + return 0 + fi + if [[ ! -d "${IRCKIT_XCFRAMEWORK}" ]]; then + echo "[embed] IRCKIT_XCFRAMEWORK path not found: ${IRCKIT_XCFRAMEWORK}" + return 0 + fi + + local slice="" + # Prefer universal slice if available; otherwise match host arch + if [[ -d "${IRCKIT_XCFRAMEWORK}/macos-arm64_x86_64/IRCKit.framework" ]]; then + slice="${IRCKIT_XCFRAMEWORK}/macos-arm64_x86_64/IRCKit.framework" + else + case "$ARCH_CURRENT" in + arm64) + if [[ -d "${IRCKIT_XCFRAMEWORK}/macos-arm64/IRCKit.framework" ]]; then + slice="${IRCKIT_XCFRAMEWORK}/macos-arm64/IRCKit.framework" + fi + ;; + x86_64) + if [[ -d "${IRCKIT_XCFRAMEWORK}/macos-x86_64/IRCKit.framework" ]]; then + slice="${IRCKIT_XCFRAMEWORK}/macos-x86_64/IRCKit.framework" + fi + ;; + esac + fi + + if [[ -z "$slice" ]]; then + echo "[embed] Could not find matching IRCKit.framework slice in xcframework" + return 0 + fi + + local dest="$CONTENTS_DIR/Frameworks/IRCKit.framework" + rm -rf "$dest" + echo "[embed] Embedding IRCKit.framework slice: $slice" + cp -R "$slice" "$dest" + # Sign the embedded framework + xcrun codesign --force -s - "$dest" +} + +embed_royalvnckit_framework() { + if [[ "${PROLE_ENABLE_RoyalVNCKit:-0}" != "1" ]]; then + return 0 + fi + # Embed RoyalVNCKit.xcframework slice into Contents/Frameworks if available + if [[ -z "${ROYALVNCKIT_XCFRAMEWORK:-}" ]]; then + return 0 + fi + if [[ ! -d "${ROYALVNCKIT_XCFRAMEWORK}" ]]; then + echo "[embed] ROYALVNCKIT_XCFRAMEWORK path not found: ${ROYALVNCKIT_XCFRAMEWORK}" + return 0 + fi + + local slice="" + # Prefer universal slice if present; otherwise pick matching arch + if [[ -d "${ROYALVNCKIT_XCFRAMEWORK}/macos-arm64_x86_64/RoyalVNCKit.framework" ]]; then + slice="${ROYALVNCKIT_XCFRAMEWORK}/macos-arm64_x86_64/RoyalVNCKit.framework" + else + case "$ARCH_CURRENT" in + arm64) + if [[ -d "${ROYALVNCKIT_XCFRAMEWORK}/macos-arm64/RoyalVNCKit.framework" ]]; then + slice="${ROYALVNCKIT_XCFRAMEWORK}/macos-arm64/RoyalVNCKit.framework" + fi + ;; + x86_64) + if [[ -d "${ROYALVNCKIT_XCFRAMEWORK}/macos-x86_64/RoyalVNCKit.framework" ]]; then + slice="${ROYALVNCKIT_XCFRAMEWORK}/macos-x86_64/RoyalVNCKit.framework" + fi + ;; + esac + fi + + if [[ -z "$slice" ]]; then + echo "[embed] Could not find matching RoyalVNCKit.framework slice in xcframework" + return 0 + fi + + local dest="$CONTENTS_DIR/Frameworks/RoyalVNCKit.framework" + rm -rf "$dest" + echo "[embed] Embedding RoyalVNCKit.framework slice: $slice" + cp -R "$slice" "$dest" + # Sign the embedded framework + xcrun codesign --force -s - "$dest" +} + 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 @@ -270,11 +711,22 @@ build_one_arch() { swiftc_compile "$arch" "$bin" mkdir -p "$MACOS_DIR" cp "$bin" "$MACOS_DIR/${APP_NAME}" + embed_irckit_framework + embed_royalvnckit_framework 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:" + /usr/bin/find "$CONTENTS_DIR/Frameworks" -maxdepth 1 -type d -name "*.framework" -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 bin_arm64="$BUILD_DIR/${APP_NAME}-arm64" @@ -283,8 +735,14 @@ build_universal() { swiftc_compile x86_64 "$bin_x86" mkdir -p "$MACOS_DIR" xcrun lipo -create -output "$MACOS_DIR/${APP_NAME}" "$bin_arm64" "$bin_x86" + embed_irckit_framework + embed_royalvnckit_framework 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:" + /usr/bin/find "$CONTENTS_DIR/Frameworks" -maxdepth 1 -type d -name "*.framework" -exec basename {} \; | sed 's/^/ - /' + cleanup_dependencies } clean() {