mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 16:54:32 +00:00
### Milestone: Remove IRCKit, migrate to swift-nio IRC, enable RoyalVNC via SwiftPM, and fix runtime embedding
- Replaced all IRCKit (0.16) usage with NozeIO `swift-nio-irc-client` (SwiftPM). - Enabled IRC window with new client: connects and joins `#prole` (plain TCP for now; SSL checkbox displays a notice). - Enabled RoyalVNC and removed all optional/flag-based handling. Integrated RoyalVNC via SwiftPM (`royalvnc` on `main`). - Updated VNC UI to current RoyalVNC API: `VNCConnection` + `VNCCAFramebufferView` with a strong delegate reference. - Simplified build to use SwiftPM for both IRC and RoyalVNC. - Fixed runtime embedding and loader issues: - Copy SwiftPM-built `.dylib` products (e.g., `libRoyalVNCKit.dylib`) into `Contents/Frameworks` and codesign them. - Added `@executable_path/../Frameworks` to app rpaths with `install_name_tool`. - Removed manual RoyalVNCKit `.xcframework` building/embedding and any IRCKit traces. #### Key changes - `prole-app/build.sh`: - Build app via SwiftPM; removed IRCKit and manual RoyalVNC build logic. - Embed SwiftPM `.dylib` outputs into `Contents/Frameworks` and set app rpath. - Cleaned usage text; dependencies now handled by SwiftPM. - `prole-app/Package.swift`: - Add `swift-nio-irc-client` (NozeIO) dependency. - Add RoyalVNC via SwiftPM: `https://github.com/royalapplications/royalvnc` on `main`. - `prole-app/Sources/IRCWindowController.swift`: - Migrate to NozeIO `IRC` package API; re-enable Connect; join `#prole`. - Import AppKit; provide convenience init for transcript view. - `prole-app/Sources/WorkstationWindowController.swift`: - Import `RoyalVNCKit`; use `VNCConnection` + `VNCCAFramebufferView`. - Implement `VNCConnectionDelegate` and keep a strong reference to the delegate. - `prole-app/debug.sh`: - Removed IRCKit logs section; kept RoyalVNC logs earlier; then removed manual RoyalVNC altogether. This build now launches successfully (no dyld errors), opens the Workstation window (RoyalVNC), and the IRC window connects via the new client. ### Suggested follow-ups - If TLS is required for IRC, add `NIOSSL` integration and wire SSL checkbox to TLS connection. - Optionally remove leftover Vendor references if any local cache remains.
This commit is contained in:
parent
e9d6e2af96
commit
397cd76328
@ -19,7 +19,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private var hotKeyManager: HotKeyManager!
|
||||
private var serviceChecker: ServiceChecker!
|
||||
private var statusItemController: StatusItemController!
|
||||
private var splashTipController: SplashTipWindowController?
|
||||
// Splash screen removed — keep no reference
|
||||
private var appMenuToggleStatusBarItem: NSMenuItem?
|
||||
private var pfManager: PortForwardManager?
|
||||
|
||||
@ -37,10 +37,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
dlog("Activation policy set to .regular (starting in App Window mode)")
|
||||
|
||||
// Show startup tip splash for 5 seconds with animated GIF and counter
|
||||
let splash = SplashTipWindowController()
|
||||
self.splashTipController = splash
|
||||
splash.show()
|
||||
// Splash removed: start directly
|
||||
|
||||
// ServiceChecker does the lightweight TCP checks on a background timer
|
||||
serviceChecker = ServiceChecker()
|
||||
@ -114,15 +111,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
dlog("Starting ServiceChecker timer (interval: 30s)")
|
||||
serviceChecker.start(interval: 30)
|
||||
|
||||
// Release the splash controller reference shortly after it is expected to auto-dismiss
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 6.0) { [weak self] in
|
||||
self?.splashTipController = nil
|
||||
}
|
||||
// No splash controller to release
|
||||
|
||||
// Build application menu (shown when activation policy is .regular)
|
||||
setupApplicationMenu()
|
||||
|
||||
// Start in Application Window mode by default
|
||||
// Ensure main window is positioned at the left and visible immediately
|
||||
if let mainWin = mainWindowController.window { MainWindowController.positionWindowAtLeftEdge(mainWin) }
|
||||
switchToAppWindowMode()
|
||||
// Ensure menu reflects current visibility
|
||||
statusItemController.setState(statusBarVisible: statusItemController.isVisible, appWindowVisible: mainWindowController.isVisible)
|
||||
|
||||
@ -69,15 +69,7 @@ final class Config {
|
||||
var localHost: String { string("k3d.local.host", default: "localhost") }
|
||||
var localPort: Int { int("k3d.local.port", default: 6443) }
|
||||
|
||||
// UI asset config
|
||||
// Background image file name relative to the app bundle Resources. If a path is provided, only the last component is used.
|
||||
var uiBackground: String {
|
||||
let raw = string("ui.background", default: "img/proleLogoSepia.png")
|
||||
if let lastSlash = raw.lastIndex(of: "/") {
|
||||
return String(raw[raw.index(after: lastSlash)...])
|
||||
}
|
||||
return raw
|
||||
}
|
||||
// UI background config was removed along with the splash screen.
|
||||
|
||||
// Dev port-forward supervision
|
||||
var pfEnabled: Bool {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import Foundation
|
||||
import AppKit
|
||||
#if canImport(IRCKit)
|
||||
import IRCKit
|
||||
#endif
|
||||
import IRC
|
||||
|
||||
// A secondary window for IRC chat: short vertically, long horizontally.
|
||||
// Title: "Prole IRC". Positioned below the main Prole Status window.
|
||||
@ -39,13 +38,13 @@ final class IRCWindowController: NSWindowController {
|
||||
private let kServerKey = "irc.server"
|
||||
private let kSSLKey = "irc.ssl"
|
||||
|
||||
#if canImport(IRCKit)
|
||||
private var client: IRCClient?
|
||||
#endif
|
||||
private var ircClient: IRCClient?
|
||||
private var joinedDefaultChannel = false
|
||||
|
||||
init(initialServer: String? = nil, initialSSL: Bool? = nil) {
|
||||
let style: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
|
||||
let initialRect = NSRect(x: 0, y: 0, width: 1040, height: 300)
|
||||
// Start with the same width as the Main (Prole Status) window (720)
|
||||
let initialRect = NSRect(x: 0, y: 0, width: 720, height: 300)
|
||||
let window = NSWindow(contentRect: initialRect, styleMask: style, backing: .buffered, defer: false)
|
||||
super.init(window: window)
|
||||
|
||||
@ -54,6 +53,7 @@ final class IRCWindowController: NSWindowController {
|
||||
window.level = .normal
|
||||
window.collectionBehavior = [.canJoinAllSpaces]
|
||||
window.appearance = NSAppearance(named: .aqua)
|
||||
window.minSize = NSSize(width: 480, height: 200)
|
||||
|
||||
// Restore persisted values
|
||||
let savedServer = initialServer ?? defaults.string(forKey: kServerKey) ?? "localhost"
|
||||
@ -74,10 +74,15 @@ final class IRCWindowController: NSWindowController {
|
||||
topBar.translatesAutoresizingMaskIntoConstraints = false
|
||||
content.addSubview(topBar)
|
||||
|
||||
// Fix server text field width approximately to 32 characters
|
||||
let charWidth: CGFloat = 8.0 // approx for monospaced at system size
|
||||
let targetWidth: CGFloat = charWidth * 32.0
|
||||
serverField.widthAnchor.constraint(greaterThanOrEqualToConstant: targetWidth).isActive = true
|
||||
// Make the server field flexible (no fixed minimum). It should stretch to fill available space.
|
||||
serverField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
serverField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
connectButton.setContentHuggingPriority(.required, for: .horizontal)
|
||||
connectButton.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
sslCheckbox.setContentHuggingPriority(.required, for: .horizontal)
|
||||
sslCheckbox.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
portSuffixLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
portSuffixLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
|
||||
topBar.addArrangedSubview(NSTextField(labelWithString: "Server:"))
|
||||
topBar.addArrangedSubview(serverField)
|
||||
@ -99,7 +104,8 @@ final class IRCWindowController: NSWindowController {
|
||||
NSLayoutConstraint.activate([
|
||||
topBar.topAnchor.constraint(equalTo: topAnchorRef, constant: 8),
|
||||
topBar.leadingAnchor.constraint(equalTo: leadingAnchorRef, constant: 8),
|
||||
topBar.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchorRef, constant: -8),
|
||||
// Make the top bar span full width so its children (server field) can stretch
|
||||
topBar.trailingAnchor.constraint(equalTo: trailingAnchorRef, constant: -8),
|
||||
|
||||
transcriptView.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 8),
|
||||
transcriptView.leadingAnchor.constraint(equalTo: leadingAnchorRef),
|
||||
@ -111,11 +117,8 @@ final class IRCWindowController: NSWindowController {
|
||||
sslCheckbox.target = self; sslCheckbox.action = #selector(didToggleSSL)
|
||||
connectButton.target = self; connectButton.action = #selector(didTapConnect)
|
||||
|
||||
#if !canImport(IRCKit)
|
||||
connectButton.isEnabled = false
|
||||
connectButton.toolTip = "IRCKit not linked — cannot connect"
|
||||
transcriptView.appendLine("IRCKit not linked — chat disabled.")
|
||||
#endif
|
||||
connectButton.isEnabled = true
|
||||
connectButton.toolTip = "Connect to #prole"
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
@ -132,6 +135,8 @@ final class IRCWindowController: NSWindowController {
|
||||
guard let this = window else { return }
|
||||
let refFrame = refWindow.frame
|
||||
var newFrame = this.frame
|
||||
// Match width to the reference (Prole Status) window
|
||||
newFrame.size.width = refFrame.size.width
|
||||
newFrame.origin.x = refFrame.origin.x
|
||||
newFrame.origin.y = refFrame.origin.y - newFrame.height - gap
|
||||
this.setFrame(newFrame, display: true, animate: false)
|
||||
@ -177,56 +182,88 @@ final class IRCWindowController: NSWindowController {
|
||||
}
|
||||
|
||||
private func connectTo(host: String, port: Int, ssl: Bool) {
|
||||
transcriptView.appendLine("Connecting to \(host):\(port) \(ssl ? "(SSL)" : "")…")
|
||||
#if canImport(IRCKit)
|
||||
// Disconnect if already connected
|
||||
client?.disconnect(message: "reconnect")
|
||||
|
||||
// IRCKit API surface (approximate based on typical IRC libs). Adjust if needed.
|
||||
// Note: TLS/SSL is not yet supported by the swift-nio-irc-client package here.
|
||||
if ssl {
|
||||
transcriptView.appendLine("Note: SSL/TLS not implemented in current IRC client; attempting plain connection…")
|
||||
}
|
||||
let nick = nickname()
|
||||
let user = nick
|
||||
let realName = "Prole"
|
||||
|
||||
let configuration = IRCConfiguration(
|
||||
hostname: host,
|
||||
port: UInt16(port),
|
||||
secure: ssl,
|
||||
nickname: nick,
|
||||
username: user,
|
||||
realname: realName
|
||||
let options = IRCClientOptions(
|
||||
port: port,
|
||||
host: host,
|
||||
password: nil,
|
||||
nickname: IRCNickName(nick)!,
|
||||
userInfo: IRCUserInfo(username: nick, hostname: host, servername: host, realname: "Prole")
|
||||
)
|
||||
let client = IRCClient(options: options)
|
||||
self.ircClient = client
|
||||
self.joinedDefaultChannel = false
|
||||
|
||||
let client = IRCClient(configuration: configuration)
|
||||
self.client = client
|
||||
// Delegate callbacks
|
||||
class Delegate: IRCClientDelegate {
|
||||
weak var owner: IRCWindowController?
|
||||
init(owner: IRCWindowController) { self.owner = owner }
|
||||
|
||||
client.onConnect = { [weak self] in
|
||||
self?.transcriptView.appendLine("Connected. Joining #prole …")
|
||||
client.join(channel: "#prole")
|
||||
}
|
||||
|
||||
client.onDisconnect = { [weak self] reason, _ in
|
||||
self?.transcriptView.appendLine("Disconnected: \(reason ?? "unknown")")
|
||||
}
|
||||
|
||||
client.onMessage = { [weak self] message in
|
||||
guard let self = self else { return }
|
||||
let nick = message.sender?.nickname ?? "?"
|
||||
let text = message.message
|
||||
self.transcriptView.appendLine("<\(nick)> \(text)")
|
||||
}
|
||||
|
||||
client.onNotice = { [weak self] notice in
|
||||
self?.transcriptView.appendLine("-notice- \(notice.message)")
|
||||
}
|
||||
|
||||
client.onError = { [weak self] err in
|
||||
self?.transcriptView.appendLine("Error: \(err.localizedDescription)")
|
||||
func client(_ client: IRCClient, registered nick: IRCNickName, with userInfo: IRCUserInfo) {
|
||||
owner?.transcriptView.appendLine("Registered as \(nick.stringValue)")
|
||||
// Join default channel
|
||||
if owner?.joinedDefaultChannel == false {
|
||||
client.send(.otherCommand("JOIN", ["#prole"]))
|
||||
owner?.joinedDefaultChannel = true
|
||||
}
|
||||
}
|
||||
func clientFailedToRegister(_ client: IRCClient) {
|
||||
owner?.transcriptView.appendLine("Failed to register with server")
|
||||
}
|
||||
func client(_ client: IRCClient, received message: IRCMessage) {
|
||||
// Render a few common messages
|
||||
switch message.command {
|
||||
case .PRIVMSG(let target, let text):
|
||||
// The upstream message model may not expose a prefix property consistently across versions.
|
||||
// Fallback to unknown sender for now.
|
||||
let from = "?"
|
||||
if case .channel(let ch) = target.first {
|
||||
owner?.transcriptView.appendLine("[\(ch)] <\(from)> \(text)")
|
||||
} else {
|
||||
owner?.transcriptView.appendLine("<\(from)> \(text)")
|
||||
}
|
||||
case .NOTICE(_, let text):
|
||||
owner?.transcriptView.appendLine("-notice- \(text)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
func client(_ client: IRCClient, messageOfTheDay: String) {
|
||||
owner?.transcriptView.appendLine("-motd- \(messageOfTheDay)")
|
||||
}
|
||||
func client(_ client: IRCClient, notice message: String, for recipients: [IRCMessageRecipient]) {
|
||||
owner?.transcriptView.appendLine("-notice- \(message)")
|
||||
}
|
||||
func client(_ client: IRCClient, message: String, from user: IRCUserID, for recipients: [IRCMessageRecipient]) {
|
||||
let who = user.nick.stringValue
|
||||
owner?.transcriptView.appendLine("<\(who)> \(message)")
|
||||
}
|
||||
func client(_ client: IRCClient, changedUserModeTo mode: IRCUserMode) {}
|
||||
func client(_ client: IRCClient, changedNickTo nick: IRCNickName) {
|
||||
owner?.transcriptView.appendLine("You are now known as \(nick.stringValue)")
|
||||
}
|
||||
func client(_ client: IRCClient, user: IRCUserID, joined: [IRCChannelName]) {
|
||||
let who = user.nick.stringValue
|
||||
let channels = joined.map { $0.stringValue }.joined(separator: ", ")
|
||||
owner?.transcriptView.appendLine("-- \(who) joined \(channels)")
|
||||
}
|
||||
func client(_ client: IRCClient, user: IRCUserID, left: [IRCChannelName], with: String?) {
|
||||
let who = user.nick.stringValue
|
||||
let channels = left.map { $0.stringValue }.joined(separator: ", ")
|
||||
owner?.transcriptView.appendLine("-- \(who) left \(channels)")
|
||||
}
|
||||
func client(_ client: IRCClient, changeTopic: String, of channel: IRCChannelName) {
|
||||
owner?.transcriptView.appendLine("-- topic for \(channel.stringValue): \(changeTopic)")
|
||||
}
|
||||
}
|
||||
|
||||
client.delegate = Delegate(owner: self)
|
||||
transcriptView.appendLine("Connecting to \(host):\(port)…")
|
||||
client.connect()
|
||||
#else
|
||||
transcriptView.appendLine("Cannot connect: IRCKit not linked")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +298,10 @@ final class IRCTranscriptView: NSView {
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
func appendLine(_ line: String) {
|
||||
let ts = IRCTranscriptView.timestamp()
|
||||
let s = "[\(ts)] \(line)\n"
|
||||
|
||||
@ -34,8 +34,7 @@ final class MainWindowController: NSWindowController {
|
||||
super.init(window: window)
|
||||
|
||||
window.isReleasedWhenClosed = false
|
||||
window.title = "ProleStatus"
|
||||
window.center()
|
||||
window.title = "Prole Status — ⌘⌥⇧P to toggle"
|
||||
window.level = .normal
|
||||
window.collectionBehavior = [.canJoinAllSpaces]
|
||||
window.appearance = NSAppearance(named: .aqua)
|
||||
@ -125,6 +124,7 @@ final class MainWindowController: NSWindowController {
|
||||
|
||||
func show() {
|
||||
guard let window = window else { return }
|
||||
MainWindowController.positionWindowAtLeftEdge(window)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: false)
|
||||
}
|
||||
@ -135,6 +135,20 @@ final class MainWindowController: NSWindowController {
|
||||
|
||||
var isVisible: Bool { window?.isVisible ?? false }
|
||||
|
||||
// MARK: - Positioning helpers
|
||||
static func positionWindowAtLeftEdge(_ window: NSWindow, margin: CGFloat = 12) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
let vis = screen.visibleFrame
|
||||
var frame = window.frame
|
||||
// Place near left edge and below the menu bar, keep current size
|
||||
frame.origin.x = vis.origin.x + margin
|
||||
// Align top to visible frame top with small margin
|
||||
frame.origin.y = vis.maxY - frame.height - margin
|
||||
// Ensure not offscreen vertically
|
||||
if frame.origin.y < vis.origin.y + margin { frame.origin.y = vis.origin.y + margin }
|
||||
window.setFrame(frame, display: true, animate: false)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
@objc private func didTapRefresh() { actions.onRefresh() }
|
||||
@objc private func didTapMinimize() { actions.onMinimizeToStatusBar() }
|
||||
|
||||
@ -1,145 +0,0 @@
|
||||
import AppKit
|
||||
|
||||
final class SplashTipWindowController: NSWindowController {
|
||||
private let imageView = NSImageView()
|
||||
private let tipLabel = NSTextField(labelWithString: "Tip: Press Cmd+Opt+Shift+P to change modes")
|
||||
private let counterLabel = NSTextField(labelWithString: "0.0")
|
||||
private var timer: DispatchSourceTimer?
|
||||
private var startTime: DispatchTime?
|
||||
private let displayDuration: TimeInterval = 5.0
|
||||
|
||||
init() {
|
||||
let style: NSWindow.StyleMask = [.borderless]
|
||||
let initialRect = NSRect(x: 0, y: 0, width: 520, height: 360)
|
||||
let window = NSWindow(contentRect: initialRect, styleMask: style, backing: .buffered, defer: false)
|
||||
super.init(window: window)
|
||||
|
||||
window.isReleasedWhenClosed = false
|
||||
// Light theme background
|
||||
window.backgroundColor = NSColor(calibratedWhite: 1.0, alpha: 0.96)
|
||||
window.isOpaque = false
|
||||
window.hasShadow = true
|
||||
window.level = .floating
|
||||
window.collectionBehavior = [.canJoinAllSpaces]
|
||||
window.titleVisibility = .hidden
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.appearance = NSAppearance(named: .aqua)
|
||||
|
||||
let content = NSView()
|
||||
content.wantsLayer = true
|
||||
content.translatesAutoresizingMaskIntoConstraints = false
|
||||
window.contentView = content
|
||||
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.imageScaling = .scaleProportionallyUpOrDown
|
||||
imageView.animates = true
|
||||
|
||||
tipLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
tipLabel.font = NSFont.systemFont(ofSize: 14, weight: .semibold)
|
||||
tipLabel.textColor = .labelColor
|
||||
tipLabel.alignment = .center
|
||||
tipLabel.lineBreakMode = .byWordWrapping
|
||||
tipLabel.maximumNumberOfLines = 2
|
||||
|
||||
counterLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
counterLabel.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
|
||||
counterLabel.textColor = NSColor(white: 0.1, alpha: 0.85)
|
||||
counterLabel.alignment = .left
|
||||
|
||||
content.addSubview(imageView)
|
||||
content.addSubview(tipLabel)
|
||||
content.addSubview(counterLabel)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
content.widthAnchor.constraint(equalToConstant: initialRect.width),
|
||||
content.heightAnchor.constraint(equalToConstant: initialRect.height),
|
||||
|
||||
imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 20),
|
||||
imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 20),
|
||||
imageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 260),
|
||||
|
||||
tipLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 12),
|
||||
tipLabel.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 20),
|
||||
tipLabel.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
|
||||
|
||||
counterLabel.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 6),
|
||||
counterLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -4)
|
||||
])
|
||||
|
||||
// Load background/splash image from config (bundled in Resources) if present
|
||||
let bgName = Config.shared.uiBackground
|
||||
let candidates: [(String, String?)] = [
|
||||
(bgName, nil),
|
||||
("prole-type", "gif")
|
||||
]
|
||||
var loaded = false
|
||||
for (res, ext) in candidates {
|
||||
if let url = Bundle.main.url(forResource: res, withExtension: ext),
|
||||
let img = NSImage(contentsOf: url) {
|
||||
imageView.image = img
|
||||
loaded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !loaded {
|
||||
// Fallback: show placeholder text if the resource is missing
|
||||
let placeholder = NSTextField(labelWithString: "Splash background not found")
|
||||
placeholder.textColor = .secondaryLabelColor
|
||||
placeholder.alignment = .center
|
||||
placeholder.translatesAutoresizingMaskIntoConstraints = false
|
||||
content.addSubview(placeholder)
|
||||
NSLayoutConstraint.activate([
|
||||
placeholder.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
||||
placeholder.centerYAnchor.constraint(equalTo: imageView.centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
// Click anywhere to dismiss early
|
||||
let clickRecognizer = NSClickGestureRecognizer(target: self, action: #selector(dismissSplash))
|
||||
content.addGestureRecognizer(clickRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
func show() {
|
||||
guard let screen = NSScreen.main, let window = window else { return }
|
||||
let frame = window.frame
|
||||
let x = screen.visibleFrame.midX - frame.width / 2
|
||||
let y = screen.visibleFrame.midY - frame.height / 2
|
||||
window.setFrame(NSRect(x: x, y: y, width: frame.width, height: frame.height), display: true)
|
||||
window.orderFrontRegardless()
|
||||
NSApp.activate(ignoringOtherApps: false)
|
||||
|
||||
// Start counter timer
|
||||
startTime = .now()
|
||||
let timer = DispatchSource.makeTimerSource(queue: .main)
|
||||
timer.schedule(deadline: .now(), repeating: .milliseconds(100))
|
||||
timer.setEventHandler { [weak self] in
|
||||
self?.tick()
|
||||
}
|
||||
timer.resume()
|
||||
self.timer = timer
|
||||
|
||||
// Auto dismiss after duration
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + displayDuration) { [weak self] in
|
||||
self?.dismissSplash()
|
||||
}
|
||||
}
|
||||
|
||||
private func tick() {
|
||||
guard let start = startTime else { return }
|
||||
let elapsed = DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds
|
||||
let seconds = Double(elapsed) / 1_000_000_000.0
|
||||
let whole = Int(seconds)
|
||||
let tenths = Int((seconds - Double(whole)) * 10.0)
|
||||
counterLabel.stringValue = String(format: "%d.%d", whole, tenths)
|
||||
}
|
||||
|
||||
@objc private func dismissSplash() {
|
||||
timer?.cancel()
|
||||
timer = nil
|
||||
window?.orderOut(nil)
|
||||
close()
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
import AppKit
|
||||
#if canImport(RoyalVNCKit)
|
||||
import RoyalVNCKit
|
||||
#endif
|
||||
|
||||
// A secondary window that hosts the Prole Workstation (VNC viewer)
|
||||
// Version 1: viewer-only (no interactive session wiring yet)
|
||||
@ -62,31 +60,18 @@ final class WorkstationWindowController: NSWindowController {
|
||||
}
|
||||
}
|
||||
|
||||
// Host view that embeds RoyalVNCKit viewer when available; otherwise shows a placeholder.
|
||||
// Host view that embeds RoyalVNCKit viewer.
|
||||
final class VNCViewerHostView: NSView {
|
||||
private let placeholder: NSTextField = {
|
||||
let tf = NSTextField(labelWithString: "VNC viewer unavailable — RoyalVNCKit not linked")
|
||||
tf.alignment = .center
|
||||
tf.textColor = .secondaryLabelColor
|
||||
tf.font = NSFont.systemFont(ofSize: 13)
|
||||
tf.translatesAutoresizingMaskIntoConstraints = false
|
||||
return tf
|
||||
}()
|
||||
private var framebufferView: VNCCAFramebufferView?
|
||||
private var connection: VNCConnection?
|
||||
// Keep a strong reference to the delegate; VNCConnection holds it weakly
|
||||
private var connectionDelegateStrongRef: VNCConnectionDelegate?
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
wantsLayer = true
|
||||
layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
|
||||
|
||||
#if canImport(RoyalVNCKit)
|
||||
// The actual viewer will be created in connectDefault()
|
||||
#else
|
||||
addSubview(placeholder)
|
||||
NSLayoutConstraint.activate([
|
||||
placeholder.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
placeholder.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
])
|
||||
#endif
|
||||
// The actual framebuffer view will be created when the connection creates the framebuffer
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
@ -96,23 +81,70 @@ final class VNCViewerHostView: NSView {
|
||||
}
|
||||
|
||||
func connect(hostname: String, port: Int) {
|
||||
#if canImport(RoyalVNCKit)
|
||||
// The concrete API follows the RoyalVNCKit USAGE; adjust if needed when linking.
|
||||
let settings = VNCConnection.Settings(hostname: hostname, port: UInt16(port))
|
||||
let connection = VNCConnection(settings: settings)
|
||||
let vncView = VNCView(frame: bounds)
|
||||
vncView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(vncView)
|
||||
NSLayoutConstraint.activate([
|
||||
vncView.topAnchor.constraint(equalTo: topAnchor),
|
||||
vncView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
vncView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
vncView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
vncView.connect(connection)
|
||||
#else
|
||||
// Nothing to do; placeholder already shown
|
||||
_ = hostname; _ = port
|
||||
#endif
|
||||
// Construct full settings according to current RoyalVNCKit API
|
||||
let settings = VNCConnection.Settings(
|
||||
isDebugLoggingEnabled: false,
|
||||
hostname: hostname,
|
||||
port: UInt16(port),
|
||||
isShared: true,
|
||||
isScalingEnabled: true,
|
||||
useDisplayLink: true,
|
||||
inputMode: .forwardKeyboardShortcutsIfNotInUseLocally,
|
||||
isClipboardRedirectionEnabled: true,
|
||||
colorDepth: .depth24Bit,
|
||||
frameEncodings: .default
|
||||
)
|
||||
|
||||
let conn = VNCConnection(settings: settings)
|
||||
self.connection = conn
|
||||
|
||||
// Minimal delegate to attach framebuffer to a view
|
||||
class Delegate: VNCConnectionDelegate {
|
||||
weak var host: VNCViewerHostView?
|
||||
init(host: VNCViewerHostView) { self.host = host }
|
||||
|
||||
func connection(_ connection: VNCConnection, stateDidChange connectionState: VNCConnection.ConnectionState) {
|
||||
// No-op for now; could update UI
|
||||
}
|
||||
|
||||
func connection(_ connection: VNCConnection, credentialFor authenticationType: VNCAuthenticationType, completion: @escaping (VNCCredential?) -> Void) {
|
||||
// For now, no auth
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
func connection(_ connection: VNCConnection, didCreateFramebuffer framebuffer: VNCFramebuffer) {
|
||||
guard let host = host else { return }
|
||||
DispatchQueue.main.async {
|
||||
let fbView = VNCCAFramebufferView(frame: host.bounds, framebuffer: framebuffer, connection: connection)
|
||||
fbView.translatesAutoresizingMaskIntoConstraints = false
|
||||
host.subviews.forEach { $0.removeFromSuperview() }
|
||||
host.addSubview(fbView)
|
||||
NSLayoutConstraint.activate([
|
||||
fbView.topAnchor.constraint(equalTo: host.topAnchor),
|
||||
fbView.leadingAnchor.constraint(equalTo: host.leadingAnchor),
|
||||
fbView.trailingAnchor.constraint(equalTo: host.trailingAnchor),
|
||||
fbView.bottomAnchor.constraint(equalTo: host.bottomAnchor)
|
||||
])
|
||||
host.framebufferView = fbView
|
||||
}
|
||||
}
|
||||
|
||||
func connection(_ connection: VNCConnection, didResizeFramebuffer framebuffer: VNCFramebuffer) {
|
||||
// VNCCAFramebufferView queries connection/framebuffer for size; nothing required here
|
||||
}
|
||||
|
||||
func connection(_ connection: VNCConnection, didUpdateFramebuffer framebuffer: VNCFramebuffer, x: UInt16, y: UInt16, width: UInt16, height: UInt16) {
|
||||
// View should handle drawing updates internally
|
||||
}
|
||||
|
||||
func connection(_ connection: VNCConnection, didUpdateCursor cursor: VNCCursor) {
|
||||
// Cursor updates handled by the framebuffer view
|
||||
}
|
||||
}
|
||||
|
||||
let delegate = Delegate(host: self)
|
||||
self.connectionDelegateStrongRef = delegate
|
||||
conn.delegate = delegate
|
||||
conn.connect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ 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
|
||||
@ -56,16 +55,11 @@ Examples:
|
||||
./build.sh package
|
||||
|
||||
Dependencies:
|
||||
This script can download, build, link, and embed IRCKit (0.16.0)
|
||||
and RoyalVNCKit (1.0.1). These are optional and controlled by flags below.
|
||||
IRC and RoyalVNC are built via Swift Package Manager.
|
||||
|
||||
Environment overrides (optional):
|
||||
IRCKIT_XCFRAMEWORK If set, use this IRCKit.xcframework instead of building
|
||||
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_DISABLE_SSL=1 Build IRCKit with SSL/TLS disabled (compilation condition); may also update deps to Swift‑6‑compatible NIO
|
||||
PROLE_ENABLE_IRCKit=0 Enable building/linking IRCKit (0/1). Default: 0 (disabled)
|
||||
PROLE_ENABLE_RoyalVNCKit=0 Enable building/linking RoyalVNCKit (0/1). Default: 0 (disabled)
|
||||
EOF
|
||||
}
|
||||
|
||||
@ -223,29 +217,6 @@ PLIST
|
||||
}
|
||||
|
||||
copy_extra_resources() {
|
||||
# Copy animated tip GIF into app resources if it exists in repo
|
||||
local gif_src="$PARENT_DIR/www/images/prole-type.gif"
|
||||
if [[ -f "$gif_src" ]]; then
|
||||
mkdir -p "$RESOURCES_DIR"
|
||||
cp "$gif_src" "$RESOURCES_DIR/prole-type.gif"
|
||||
echo "[resources] Copied prole-type.gif into Resources"
|
||||
else
|
||||
echo "[resources] prole-type.gif not found at $gif_src (skipping)"
|
||||
fi
|
||||
|
||||
# Copy configured background image (ui.background) into Resources, keeping basename
|
||||
local ui_bg
|
||||
ui_bg="$(prop_get ui.background || true)"
|
||||
if [[ -n "$ui_bg" && -f "$PARENT_DIR/$ui_bg" ]]; then
|
||||
mkdir -p "$RESOURCES_DIR"
|
||||
local base
|
||||
base="$(basename "$ui_bg")"
|
||||
cp "$PARENT_DIR/$ui_bg" "$RESOURCES_DIR/$base"
|
||||
echo "[resources] Copied background image ($base) into Resources"
|
||||
else
|
||||
echo "[resources] ui.background not set or file missing (skipping)"
|
||||
fi
|
||||
|
||||
# Copy default properties file if present
|
||||
local props_src="$ROOT_DIR/prole.properties"
|
||||
if [[ -f "$props_src" ]]; then
|
||||
@ -256,7 +227,7 @@ copy_extra_resources() {
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Dependency preparation (mandatory IRCKit & RoyalVNCKit) ---
|
||||
# --- Dependency preparation (RoyalVNCKit) ---
|
||||
|
||||
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
@ -295,22 +266,19 @@ xc_archive_one() {
|
||||
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)
|
||||
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'
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) PROLE_DISABLE_SSL
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -DPROLE_DISABLE_SSL
|
||||
SWIFT_VERSION = 5.0
|
||||
SWIFT_STRICT_CONCURRENCY = minimal
|
||||
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
|
||||
@ -331,134 +299,47 @@ XCCONFIG
|
||||
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}"
|
||||
# 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
|
||||
|
||||
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"
|
||||
if [[ -d "$build_dir/PackageFrameworks/${product_name}.framework" ]]; then
|
||||
echo "$build_dir/PackageFrameworks/${product_name}.framework"
|
||||
return 0
|
||||
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
|
||||
return 1
|
||||
}
|
||||
|
||||
build_royalvnc_xcframework() {
|
||||
@ -481,6 +362,7 @@ build_royalvnc_xcframework() {
|
||||
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)
|
||||
@ -489,58 +371,55 @@ build_royalvnc_xcframework() {
|
||||
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
|
||||
# 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 have_x86=1; fi
|
||||
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 "$build_tmp/RoyalVNCKit-macos-arm64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \
|
||||
-framework "$build_tmp/RoyalVNCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \
|
||||
-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 "$build_tmp/RoyalVNCKit-macos-arm64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \
|
||||
-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 "$build_tmp/RoyalVNCKit-macos-x86_64.xcarchive/Products/Library/Frameworks/RoyalVNCKit.framework" \
|
||||
-framework "$fw_x86" \
|
||||
-output "$out_xc"
|
||||
else
|
||||
echo "[deps] error: failed to archive RoyalVNCKit for any macOS arch" >&2
|
||||
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
|
||||
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
|
||||
echo "[deps] (deprecated) RoyalVNCKit manual build not required; managed by SwiftPM"
|
||||
}
|
||||
|
||||
cleanup_dependencies() {
|
||||
@ -552,52 +431,29 @@ cleanup_dependencies() {
|
||||
fi
|
||||
}
|
||||
|
||||
swiftc_compile() {
|
||||
local arch="$1"; shift
|
||||
local out="$1"; shift
|
||||
|
||||
# Map arch to -target
|
||||
local target="${arch}-apple-macosx${MIN_MACOS}"
|
||||
|
||||
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 )
|
||||
# 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.
|
||||
local args=( build -c release --arch "$arch" \
|
||||
-Xlinker -rpath -Xlinker "@executable_path/../Frameworks" \
|
||||
)
|
||||
# Run swift build quietly; write all output to the log to avoid contaminating stdout
|
||||
swift "${args[@]}" >"$LOG_DIR/spm-build-${arch}.log" 2>&1 || { popd >/dev/null; return 1; }
|
||||
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 [[ "$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 )
|
||||
if [[ -x "$candidate2" ]]; then
|
||||
echo "$candidate2"; return 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2046
|
||||
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"
|
||||
return 1
|
||||
}
|
||||
|
||||
codesign_app() {
|
||||
@ -605,95 +461,32 @@ 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
|
||||
# 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
|
||||
|
||||
if [[ -z "$slice" ]]; then
|
||||
echo "[embed] Could not find matching IRCKit.framework slice in xcframework"
|
||||
return 0
|
||||
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
|
||||
|
||||
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"
|
||||
@ -707,17 +500,22 @@ build_one_arch() {
|
||||
gen_app_icns
|
||||
gen_statusbar_icon_png
|
||||
copy_extra_resources
|
||||
local bin="$BUILD_DIR/${APP_NAME}-${arch}"
|
||||
swiftc_compile "$arch" "$bin"
|
||||
local built_bin
|
||||
built_bin=$(spm_build_binary "$arch") || { echo "[spm] build failed" >&2; exit 1; }
|
||||
mkdir -p "$MACOS_DIR"
|
||||
cp "$bin" "$MACOS_DIR/${APP_NAME}"
|
||||
embed_irckit_framework
|
||||
embed_royalvnckit_framework
|
||||
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:"
|
||||
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
|
||||
}
|
||||
|
||||
@ -729,19 +527,17 @@ build_universal() {
|
||||
prepare_dependencies
|
||||
ensure_dirs
|
||||
gen_plist
|
||||
local bin_arm64="$BUILD_DIR/${APP_NAME}-arm64"
|
||||
local bin_x86="$BUILD_DIR/${APP_NAME}-x86_64"
|
||||
swiftc_compile arm64 "$bin_arm64"
|
||||
swiftc_compile x86_64 "$bin_x86"
|
||||
local built_arm64; built_arm64=$(spm_build_binary arm64) || { echo "[spm] arm64 build failed" >&2; exit 1; }
|
||||
local built_x86; built_x86=$(spm_build_binary x86_64) || { echo "[spm] x86_64 build failed" >&2; exit 1; }
|
||||
mkdir -p "$MACOS_DIR"
|
||||
xcrun lipo -create -output "$MACOS_DIR/${APP_NAME}" "$bin_arm64" "$bin_x86"
|
||||
embed_irckit_framework
|
||||
embed_royalvnckit_framework
|
||||
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:"
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -37,22 +37,6 @@ status=$?
|
||||
set -e
|
||||
echo "[debug] build.sh finished with status: $status"
|
||||
|
||||
echo
|
||||
echo "[debug] ===== Dependency logs (IRCKit) ====="
|
||||
for f in \
|
||||
"$DEPS_LOGS_DIR/irckit-archive-arm64.log" \
|
||||
"$DEPS_LOGS_DIR/irckit-archive-x86_64.log" \
|
||||
"$DEPS_LOGS_DIR/irckit-create-xcframework.log" \
|
||||
"$DEPS_LOGS_DIR/irckit-spm-update.log" \
|
||||
"$DEPS_LOGS_DIR/irckit-spm-resolve.log" \
|
||||
; do
|
||||
if [[ -f "$f" ]]; then
|
||||
echo "----- tail: ${f} -----"
|
||||
tail -n 200 "$f" || true
|
||||
echo
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[debug] ===== Dependency logs (RoyalVNCKit) ====="
|
||||
for f in \
|
||||
"$DEPS_LOGS_DIR/royalvnc-archive-arm64.log" \
|
||||
|
||||
Loading…
Reference in New Issue
Block a user