mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 19:44:33 +00:00
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
826 lines
31 KiB
Bash
Executable File
826 lines
31 KiB
Bash
Executable File
#!/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_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 <<EOF
|
||
Usage: $(basename "$0") [command] [options]
|
||
|
||
Commands:
|
||
build Build ${APP_NAME}.app for the current architecture (default)
|
||
build-universal Build a universal (arm64+x86_64) ${APP_NAME}.app
|
||
clean Remove build artifacts
|
||
run Build (if needed) and run the app
|
||
debug Build (if needed) and run in foreground with verbose logs
|
||
package Zip the built app into dist/${APP_NAME}.zip
|
||
|
||
Options (for build):
|
||
--arch <arch> Build for a specific arch (arm64 or x86_64). Defaults to host arch.
|
||
|
||
Examples:
|
||
./build.sh build
|
||
./build.sh build --arch arm64
|
||
./build.sh build-universal
|
||
./build.sh run
|
||
./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.
|
||
|
||
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
|
||
}
|
||
|
||
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"
|
||
}
|
||
|
||
# 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" <<PLIST
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<key>CFBundleDevelopmentRegion</key><string>en</string>
|
||
<key>CFBundleExecutable</key><string>${APP_NAME}</string>
|
||
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
|
||
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
||
<key>CFBundleName</key><string>${APP_NAME}</string>
|
||
<key>CFBundlePackageType</key><string>APPL</string>
|
||
<key>CFBundleShortVersionString</key><string>1.0</string>
|
||
<key>CFBundleVersion</key><string>1</string>
|
||
<key>LSMinimumSystemVersion</key><string>${MIN_MACOS}</string>
|
||
<key>NSHighResolutionCapable</key><true/>
|
||
<key>NSPrincipalClass</key><string>NSApplication</string>
|
||
<key>LSApplicationCategoryType</key><string>public.app-category.developer-tools</string>
|
||
<!-- UIElement must be false so the app can present a normal window and app menu when in Application Window mode. -->
|
||
<key>LSUIElement</key><false/>
|
||
<key>CFBundleIconFile</key><string>${APP_NAME}.icns</string>
|
||
</dict>
|
||
</plist>
|
||
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
|
||
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
|
||
}
|
||
|
||
# --- Dependency preparation (mandatory IRCKit & 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" == "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
|
||
|
||
# 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 )
|
||
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
|
||
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"
|
||
}
|
||
|
||
codesign_app() {
|
||
echo "[codesign] Ad-hoc signing ${APP_DIR}"
|
||
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
|
||
gen_statusbar_icon_png
|
||
copy_extra_resources
|
||
local bin="$BUILD_DIR/${APP_NAME}-${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"
|
||
local bin_x86="$BUILD_DIR/${APP_NAME}-x86_64"
|
||
swiftc_compile arm64 "$bin_arm64"
|
||
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() {
|
||
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}"
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--arch) arch="$2"; shift 2 ;;
|
||
*) 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
|