e129f094c2
### 新增文件 - client/ios/PacketTunnel/MemoryMonitor.swift 周期打点 NE 进程内存(os_proc_available_memory iOS13+ / mach_task_basic_info 兼容 iOS12);每 10s 采样一次;availableMemory < 5MB 时输出 ⚠️ 警告;提供 summaryString() 供 handleAppMessage/getMemory IPC 返回。 - client/ios/PacketTunnel/PacketTunnel.entitlements Extension 的 NE + App Group entitlement 文件;申请说明写入注释。 - client/ios/Runner/Runner.entitlements 主 App 的 NE + App Group entitlement 文件;申请说明写入注释。 - doc/ne-entitlement-申请指引.md 完整申请操作手册:Bundle ID 清单、Apple Developer Portal 步骤、 专项审批英文申请文案(已脱敏,无红线词)、审批 lead time 说明。 ### 修改文件 - client/ios/PacketTunnel/PacketTunnelProvider.swift 完整 M3 实现(替换原骨架 TODO): · startTunnel:解析 configJson(options/App Group 两路回退)→ buildNetworkSettings → setTunnelNetworkSettings → setupTunBridge → startLibbox → startPacketBridging → memoryMonitor.start() · stopTunnel:memoryMonitor.stop() → stopLibbox → closeTunBridge · handleAppMessage:JSON IPC 协议(getStatus/getMemory/selectOutbound) · LibboxPlatformInterface 扩展(#if canImport(Libbox)): openTun 返回 socketpair libbox 侧 fd,autoDetectInterfaceControl no-op, writeLog 转 os.log,useProcFS 返回 false · packetFlow 双向桥接(readFromPacketFlow/writeToPacketFlow 循环) · parseTunAddress/parseDNSServers 从 JSON 提取网络参数 - client/ios/Runner/VpnManager.swift · localizedDescription "穿山甲 Pangolin VPN" → "Pangolin 加速"(红线词修复) · 新增 appGroup 常量(group.com.pangolin.pangolinVpn) · start() 写入 App Group UserDefaults 缓存 configJson - client/ios/Runner/Info.plist NSVPNUsageDescription "穿山甲使用 VPN…" → "Pangolin 使用网络加速通道…"(红线词修复) - client/ios/PacketTunnel/Info.plist CFBundleDisplayName "穿山甲 Tunnel" → "Pangolin 加速通道"(红线词修复) - client/ios/Runner.xcodeproj/project.pbxproj · Runner + PacketTunnel 所有 build config 添加 CODE_SIGN_ENTITLEMENTS · 新增 MemoryMonitor.swift 到 PacketTunnel Sources 构建阶段 · 新增 Runner.entitlements / PacketTunnel.entitlements / MemoryMonitor.swift 文件引用及分组 - app/kernel/build-ios.sh 添加 M3 内存裁剪决策记录:gVisor→grpc→QUIC 优先裁减顺序及内存估算, 等待真机 10min 压测结果后更新最终结论 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
7.9 KiB
Bash
171 lines
7.9 KiB
Bash
#!/usr/bin/env bash
|
||
# build-ios.sh — gomobile bind sing-box libbox → dist/ios/Libbox.xcframework
|
||
#
|
||
# 用法: ./build-ios.sh [--force]
|
||
# --force 忽略已有产物,强制重新构建
|
||
#
|
||
# 依赖:
|
||
# - macOS + Xcode(含 iOS SDK & Simulator SDK)
|
||
# - Go : VERSION 中 GO_VERSION 指定的版本
|
||
# - gomobile: 脚本自动安装至 $GOPATH/bin
|
||
#
|
||
# 产物: dist/ios/Libbox.xcframework
|
||
# 含两个 slice: iphoneos (device) + iphonesimulator (x86_64 + arm64)
|
||
#
|
||
# Build tags 说明(对齐官方 SFI — sing-box for iOS):
|
||
# with_quic — QUIC/HTTP3 协议支持(Hysteria2 必须)
|
||
# with_utls — uTLS 指纹模拟(TLS 防检测)
|
||
# with_clash_api — Clash 兼容 API(面板控制必须)
|
||
# with_gvisor — gVisor TUN 网络栈
|
||
#
|
||
# ⚠ iOS Network Extension 内存上限敏感
|
||
# 11F 实测若遇 "NEProvider memory limit exceeded" 崩溃,
|
||
# 回此脚本逐步裁剪 BUILD_TAGS,推荐优先裁减顺序:
|
||
# 1. 去掉 with_grpc → 减约 4 MB(需 gRPC 入站时保留)
|
||
# 2. 去掉 with_gvisor → 减约 2 MB(失去 gVisor TUN stack 回退,改用系统栈)
|
||
# 3. 去掉 with_quic → 仅在完全不使用 Hysteria2/QUIC 时考虑
|
||
# 裁减后执行 ./build-ios.sh --force 重出包,重新接入 Flutter 工程(见 11F)。
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# ── 加载版本锚点 ─────────────────────────────────────────────────────────────
|
||
# shellcheck source=VERSION
|
||
. "${SCRIPT_DIR}/VERSION"
|
||
|
||
# ── 参数解析 ─────────────────────────────────────────────────────────────────
|
||
FORCE=false
|
||
for arg in "$@"; do
|
||
case "${arg}" in
|
||
--force) FORCE=true ;;
|
||
*) printf 'Unknown argument: %s\n' "${arg}" >&2; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
# ── 路径配置 ─────────────────────────────────────────────────────────────────
|
||
DIST_DIR="${SCRIPT_DIR}/dist/ios"
|
||
OUT_XCFW="${DIST_DIR}/Libbox.xcframework"
|
||
BUILD_SRC="${SCRIPT_DIR}/.build/singbox"
|
||
|
||
# ── Build tags ────────────────────────────────────────────────────────────────
|
||
BUILD_TAGS="with_quic,with_utls,with_clash_api,with_gvisor"
|
||
# 取消注释以增加功能(会增大 XCFramework 体积 / 内存占用):
|
||
# BUILD_TAGS="${BUILD_TAGS},with_grpc"
|
||
# BUILD_TAGS="${BUILD_TAGS},with_ech"
|
||
# BUILD_TAGS="${BUILD_TAGS},with_wireguard"
|
||
|
||
# ── M3 内存裁剪决策记录(tsk_nsobbj_rJdy0)────────────────────────────────────
|
||
# NE 进程内存上限实测(MemoryMonitor 打点):
|
||
# 初始 build tags(with_quic,with_utls,with_clash_api,with_gvisor):
|
||
# 预估 RSS:~20-25 MB(含 Go runtime ~8MB + gVisor ~4MB + QUIC ~5MB + uTLS ~2MB + 基础~6MB)
|
||
# 旧设备(≤ A9)15MB 上限下会触顶;新设备(≥ A12)50MB 上限下可运行
|
||
#
|
||
# 若真机测试出现 "NEProvider memory limit exceeded" / jetsam 杀进程,按以下顺序裁减:
|
||
# Step 1: 去掉 with_gvisor → 改用系统 TUN 网络栈;减约 2-4 MB
|
||
# 取消注释下一行:
|
||
# BUILD_TAGS="with_quic,with_utls,with_clash_api"
|
||
# Step 2: 去掉 with_grpc → 若不需要 gRPC 入站;减约 4 MB
|
||
# 取消注释下一行:
|
||
# BUILD_TAGS="with_quic,with_utls,with_clash_api" # 已无 gvisor,此行等效
|
||
# Step 3: 去掉 with_quic → 仅在放弃 Hysteria2/QUIC 协议时考虑;减约 3 MB
|
||
# 取消注释下一行:
|
||
# BUILD_TAGS="with_utls,with_clash_api"
|
||
#
|
||
# 裁减后执行: ./build-ios.sh --force 重出 XCFramework,然后在 Xcode 中
|
||
# 重新拖入 dist/ios/Libbox.xcframework(或更新 SPM/Pods 引用)。
|
||
# 在此注释中记录最终选定的 build tags 及峰值 RSS(由 MemoryMonitor 汇报)。
|
||
#
|
||
# 当前状态:等待真机 10min 压测结果(MemoryMonitor 每 10s 打点)。
|
||
|
||
# ── 幂等检查 ─────────────────────────────────────────────────────────────────
|
||
if [[ "${FORCE}" == false && -d "${OUT_XCFW}" ]]; then
|
||
printf '✓ %s 已存在,跳过构建(传 --force 强制重建)\n' "${OUT_XCFW}"
|
||
exit 0
|
||
fi
|
||
|
||
# ── 前置检查 ─────────────────────────────────────────────────────────────────
|
||
printf '==> 检查前置依赖…\n'
|
||
|
||
if [[ "$(uname)" != "Darwin" ]]; then
|
||
printf '✗ iOS 构建仅支持 macOS\n' >&2
|
||
exit 1
|
||
fi
|
||
if ! command -v xcodebuild >/dev/null 2>&1; then
|
||
printf '✗ xcodebuild 未找到,请安装 Xcode\n' >&2
|
||
exit 1
|
||
fi
|
||
if ! command -v go >/dev/null 2>&1; then
|
||
printf '✗ go 未找到,请安装 Go %s\n' "${GO_VERSION}" >&2
|
||
exit 1
|
||
fi
|
||
if ! command -v git >/dev/null 2>&1; then
|
||
printf '✗ git 未找到\n' >&2
|
||
exit 1
|
||
fi
|
||
|
||
ACTUAL_GO="$(go version | awk '{print $3}' | sed 's/go//')"
|
||
XCODE_VER="$(xcodebuild -version 2>/dev/null | head -1)"
|
||
printf ' Go 版本: %s(期望 %s)\n' "${ACTUAL_GO}" "${GO_VERSION}"
|
||
printf ' Xcode: %s\n' "${XCODE_VER}"
|
||
|
||
# ── 安装 gomobile ─────────────────────────────────────────────────────────────
|
||
printf '==> 安装 gomobile@%s…\n' "${GOMOBILE_VERSION}"
|
||
go install "golang.org/x/mobile/cmd/gomobile@${GOMOBILE_VERSION}"
|
||
go install "golang.org/x/mobile/cmd/gobind@${GOMOBILE_VERSION}"
|
||
|
||
printf '==> gomobile init…\n'
|
||
gomobile init
|
||
|
||
# ── 准备 sing-box 源码 ────────────────────────────────────────────────────────
|
||
printf '==> 准备 sing-box %s 源码…\n' "${SINGBOX_VERSION}"
|
||
|
||
EXISTING_TAG=""
|
||
if [[ -d "${BUILD_SRC}/.git" ]]; then
|
||
EXISTING_TAG="$(git -C "${BUILD_SRC}" describe --tags --exact-match 2>/dev/null)" || EXISTING_TAG="unknown"
|
||
fi
|
||
|
||
if [[ "${EXISTING_TAG}" == "${SINGBOX_VERSION}" ]]; then
|
||
printf ' 源码已是 %s,跳过克隆\n' "${SINGBOX_VERSION}"
|
||
else
|
||
if [[ -d "${BUILD_SRC}" ]]; then
|
||
printf ' 已有源码版本 %s ≠ %s,清理后重新克隆…\n' "${EXISTING_TAG}" "${SINGBOX_VERSION}"
|
||
rm -rf "${BUILD_SRC}"
|
||
fi
|
||
mkdir -p "${BUILD_SRC%/*}"
|
||
git clone --depth 1 --branch "${SINGBOX_VERSION}" \
|
||
https://github.com/SagerNet/sing-box.git "${BUILD_SRC}"
|
||
fi
|
||
|
||
# ── 构建 XCFramework ──────────────────────────────────────────────────────────
|
||
printf '==> 构建 Libbox.xcframework\n'
|
||
printf ' target : ios,iossimulator\n'
|
||
printf ' tags : %s\n' "${BUILD_TAGS}"
|
||
|
||
mkdir -p "${DIST_DIR}"
|
||
|
||
# 若已有旧产物先清理(gomobile 输出到固定路径时不会自动覆盖目录)
|
||
if [[ -d "${OUT_XCFW}" ]]; then
|
||
rm -rf "${OUT_XCFW}"
|
||
fi
|
||
|
||
(
|
||
cd "${BUILD_SRC}"
|
||
go mod download
|
||
gomobile bind \
|
||
-target ios,iossimulator \
|
||
-tags "${BUILD_TAGS}" \
|
||
-o "${OUT_XCFW}" \
|
||
./experimental/libbox
|
||
)
|
||
|
||
printf '\n✓ 构建完成: %s\n' "${OUT_XCFW}"
|
||
printf ' Slice 验证:\n'
|
||
if command -v xcodebuild >/dev/null 2>&1; then
|
||
xcodebuild -create-xcframework -output /dev/null 2>&1 || true
|
||
fi
|
||
# 列出 XCFramework 内的 framework slice 目录
|
||
for slice_dir in "${OUT_XCFW}"/*/; do
|
||
printf ' %s\n' "${slice_dir}"
|
||
done
|