name: ShadowsocksX-NG2 options: createIntermediateGroups: true preGenCommand: python3 Scripts/dependency-metadata.py developmentLanguage: zh-Hans deploymentTarget: macOS: "15.0" settings: base: ARCHS: arm64 SWIFT_VERSION: "6.0" # 关闭所有源码本地化抽取路径及构建后的 String Catalog 自动同步。 # 本地化目录由仓库手工维护。 LOCALIZATION_EXPORT_SUPPORTED: NO CODE_SIGN_STYLE: Manual ENABLE_HARDENED_RUNTIME: YES configs: Release: # 发布基线:签名 entitlements 必须只来自项目 entitlements 文件(保持最小)。 CODE_SIGN_INJECT_BASE_ENTITLEMENTS: NO # 签名身份注入:证书与 Team 都不入库(开源仓库;写死会让 fork 与贡献者的构建去 # 找维护者的证书)。Debug 基线是 ad-hoc,无证书即可构建并跑单测;Release 基线是 # Developer ID,发布产物必须真签名。两者的本机 Team / 身份都来自 gitignore 的 # Configs/Signing.local.xcconfig,见 Configs/Signing-Debug.xcconfig 与 # Configs/Signing-Release.xcconfig。 # 所有 target 一律不设置 CODE_SIGN_IDENTITY:pbxproj 构建设置优先于 project # xcconfig,写在 target 上会静默盖掉两个基线(实测)。该约束由 # Tests/Architecture/SigningConfigurationTests.swift 守卫。 configFiles: Debug: Configs/Signing-Debug.xcconfig Release: Configs/Signing-Release.xcconfig packages: AgeSwift: url: https://github.com/qiuyuzhou/AgeSwift exactVersion: 0.1.0 targets: ShadowsocksX-NG2: type: application platform: macOS sources: # 目录即分层:Composition(组合根)/ Application(工作流与运行时适配)/ # Presentation(跨功能表现层)/ UI(视图,按子视图分目录)/ # PlatformEffects(平台效应 seam)/ Resources(plist、entitlements、资源)。 # 分层边界由 Tests/Architecture/SkeletonTests.swift 的 UI 圈定守卫断言; # 取舍与约束见 ADR 0029。 - path: App excludes: # 文档不属于 target 源码:App/ 下任何非源码文件都会被 XcodeGen 收进 # Resources 阶段并封进签名后的 app bundle(实测),因此显式排除。 - "**/*.md" - "**/*.xcstrings-mcp.lock" # 领域核心。目录即主题,根下不留平铺文件,子目录按 # GLOSSARY.md 的章节口径命名:Catalog(配置目录树与身份)/ # Subscription(订阅)/ Rules(代理规则,自定义规则在 Rules/Custom)/ # Runtime(代理运行服务:运行时文档、路径、日志、端点与探针)/ # Activation(激活候选与状态机)/ Settings(用户设置持久化)/ # SystemProxy(系统代理 typed 配置与特权 helper 契约)/ # Plugin(托管插件与用户映射)/ Credentials / LegacyImport / Diagnostics。 # 该约定由 Tests/Architecture/SkeletonTests.swift 的 Domain 布局守卫断言, # 取舍与约束见 ADR 0029。 - path: Domain # LaunchAgent 注册清单:SMAppService.agent(plistName:) 从 bundle 内 # Contents/Library/LaunchAgents/ 读取;ProgramArguments 用 bundle 相对路径 - path: LaunchAgent/com.qiuyuzhou.ShadowsocksX-NG2.agent.plist buildPhase: copyFiles: destination: wrapper subpath: Contents/Library/LaunchAgents # 特权系统代理 helper 的 LaunchDaemon 注册清单: # SMAppService.daemon(plistName:) 从 bundle 内 # Contents/Library/LaunchDaemons/ 读取;可执行路径用 BundleProgram 键 # (bundle 相对;ProgramArguments 的相对路径按 PATH 解析会 spawn 失败); # MachServices 键支撑 on-demand 激活。 - path: LaunchDaemons/com.qiuyuzhou.ShadowsocksX-NG2.systemproxy.plist buildPhase: copyFiles: destination: wrapper subpath: Contents/Library/LaunchDaemons # 外部二进制(Vendor//manifest.json 固定 tag+URL+SHA-256)。必须先跑 # Scripts/fetch-external-binaries.sh 再 xcodegen generate:文件在生成时必须已存在, # 否则 Copy Files 阶段会被静默丢弃。构建内的 preBuildScripts 只做增量复验, # 哈希或清单漂移会在构建时失败。 - path: Vendor/sslocal/sslocal buildPhase: copyFiles: destination: wrapper subpath: Contents/Helpers - path: Vendor/v2ray-plugin/v2ray-plugin buildPhase: copyFiles: destination: wrapper subpath: Contents/Helpers/Plugins - path: Vendor/ck-client/ck-client buildPhase: copyFiles: destination: wrapper subpath: Contents/Helpers/Plugins # Generated offline catalog and license/notice tree; preserve relative references. - path: .build/dependency-metadata/ThirdParty buildPhase: resources type: folder # 内置规则快照:普通构建只读本地已固定快照,离线且不重新 # 抓取或转换;缺失、损坏或版本不匹配时 verify-rule-snapshots.sh 使构建失败。 # folder reference 保留 rules//snapshot.json 结构,避免多个 # snapshot.json 在 bundle 根目录撞名。 - path: Vendor/rules buildPhase: resources type: folder preBuildScripts: - name: Verify dependency metadata basedOnDependencyAnalysis: false script: | set -e python3 "$SRCROOT/Scripts/dependency-metadata.py" --check - name: Fetch & verify external binaries basedOnDependencyAnalysis: false script: | set -e "$SRCROOT/Scripts/fetch-external-binaries.sh" - name: Verify rule snapshots basedOnDependencyAnalysis: false script: | set -e "$SRCROOT/Scripts/verify-rule-snapshots.sh" postBuildScripts: - name: Verify bundled dependency metadata basedOnDependencyAnalysis: false script: | set -e python3 "$SRCROOT/Scripts/dependency-metadata.py" --bundle "$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/ThirdParty" # 必须在 app 自身签名之前逐二进制 DevID 重签;Xcode 在所有脚本阶段之后 # 完成 app 外层签名,封存重签后的嵌套代码。 - name: Sign embedded helpers basedOnDependencyAnalysis: false script: | set -e "$SRCROOT/Scripts/sign-embedded-helpers.sh" settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2 PRODUCT_NAME: ShadowsocksX-NG2 # 模块名让位给 Core 镜像库(ShadowsocksX_NG2):两者 swiftmodule 都落 # 共享 Products 目录,重名会互相覆盖;app 可执行自身不 import 任何 # 消费方,改名零影响。 SWIFT_OBJC_BRIDGING_HEADER: App/Application/Connectivity/ConnectivityHTTPTransport.h PRODUCT_MODULE_NAME: ShadowsocksX_NG2App ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon MARKETING_VERSION: "2.0.0" # 必须 > 旧版 1.x 已发布构建的 CFBundleVersion(始终为 1),保证 build 号严格递增。 CURRENT_PROJECT_VERSION: 2 info: path: App/Resources/Info.plist properties: CFBundleShortVersionString: $(MARKETING_VERSION) CFBundleVersion: $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright: "Copyright © Qiu Yuzhou" LSMinimumSystemVersion: "15.0" LSUIElement: true NSPrincipalClass: NSApplication LSApplicationCategoryType: public.app-category.utilities entitlements: path: App/Resources/ShadowsocksX-NG2.entitlements properties: {} dependencies: - package: AgeSwift # 代理运行时 wrapper(BundleProgram):以独立可执行文件装入 # Contents/MacOS,由 LaunchAgent plist 以 bundle 相对路径引用。 - target: ShadowsocksX-NG2Agent copy: destination: wrapper subpath: Contents/MacOS # 特权系统代理 helper:独立可执行文件装入 Contents/MacOS, # 由 LaunchDaemon plist 以 bundle 相对路径引用,SMAppService.daemon 注册。 - target: ShadowsocksX-NG2SystemProxyHelper copy: destination: wrapper subpath: Contents/MacOS ShadowsocksX-NG2Core: type: library.static platform: macOS # 测试专用 app 源码镜像(去宿主化,ADR 0021):与 app target 同源编译 # (除 @main 组合根 MainApp.swift),模块名沿用 ShadowsocksX_NG2,全部 # 测试的 @testable import 零改动。测试 bundle 改链本库后不再生成 # TEST_HOST:xcodebuild test 只构建 app、不再启动 app 进程,与安装版 # 运行实例零接触。同源双编译沿用 Agent/Helper 共享 Domain 文件的既定 # 模式;Core 只被测试 target 依赖,发布链与普通构建不编译它。 sources: - path: App excludes: # 递归 glob(`**/`)而非裸文件名:XcodeGen 的 exclude 不跨 `/`,裸名只 # 匹配 App 根层。文件下移后裸名会静默失效,把 @main 组合根编进测试镜像。 - "**/MainApp.swift" - "**/Info.plist" - "**/ShadowsocksX-NG2.entitlements" - "**/Assets.xcassets" - "**/*.xcstrings-mcp.lock" - path: Domain settings: base: PRODUCT_NAME: ShadowsocksX-NG2Core SWIFT_OBJC_BRIDGING_HEADER: App/Application/Connectivity/ConnectivityHTTPTransport.h PRODUCT_MODULE_NAME: ShadowsocksX_NG2 # 静态库产物不签名(app 的 DevID 身份不适用)。 CODE_SIGNING_ALLOWED: NO dependencies: - package: AgeSwift ShadowsocksX-NG2Agent: type: tool platform: macOS sources: - path: Agent # Agent 只共享非秘密监管协议、路径、探针、统一日志发射单元与 # sslocal 输出 intake,不编译 sslocal 配置或凭据类型。显式文件路径 # 变动必须同步本清单并重新生成工程。 - path: Domain/Settings/ApplicationSupportPaths.swift - path: Domain/Runtime/RuntimePaths.swift - path: Domain/Runtime/AtomicFileWriter.swift - path: Domain/Runtime/RuntimeSupervision.swift - path: Domain/Runtime/RuntimeReloadState.swift - path: Domain/Runtime/RuntimeLog.swift - path: Domain/Runtime/SystemLog.swift - path: Domain/Runtime/LogLine.swift - path: Domain/Runtime/SslocalOutputIntake.swift - path: Domain/Runtime/EndpointHealthProbe.swift - path: Domain/Runtime/RuntimeSocketOwnershipProbe.swift settings: base: PRODUCT_NAME: ShadowsocksX-NG2Agent PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.agent ShadowsocksX-NG2SystemProxyHelper: type: tool platform: macOS sources: - path: SystemProxyHelper # helper 与 GUI 共享的 typed 契约与纯逻辑:closed typed 配置、property # list 映射、计划器、XPC 协议、请求引擎、helper 侧日志事件与统一日志 # 发射单元(helper 日志只进统一日志,ADR-0047)。helper 不含产品 # 策略:不在这些文件之外做任何域判断。以下 8 条同为显式路径,移动需同步。 - path: Domain/SystemProxy/SystemProxyConfiguration.swift - path: Domain/SystemProxy/SystemProxyPlan.swift - path: Domain/SystemProxy/SystemProxyPropertyList.swift - path: Domain/SystemProxy/SystemProxyHelperContract.swift - path: Domain/SystemProxy/SystemProxyHelperEngine.swift - path: Domain/SystemProxy/SystemProxyHelperListenerDelegate.swift - path: Domain/SystemProxy/SystemProxyHelperLog.swift - path: Domain/Runtime/SystemLog.swift settings: base: PRODUCT_NAME: ShadowsocksX-NG2SystemProxyHelper PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.systemproxy.helper ShadowsocksX-NG2Tests: type: bundle.unit-test platform: macOS sources: - path: Tests # 真实进程冒烟归 SmokeTests target,不进默认 task test 快车道。 excludes: - "Smoke" - "Fixtures" - path: Tests/Fixtures/RuleSnapshots type: folder buildPhase: resources settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.Tests # 身份随 config 基线走(Debug ad-hoc / Release DevID,见 configFiles): # 去宿主化(ADR 0021)后 xctest 包不再注入宿主 app,ad-hoc 可加载执行 # (实测),因此测试不再需要证书,CI 无需签名密钥。 info: path: Tests/Info.plist properties: {} dependencies: # 去宿主化(ADR 0021):单测链 Core 静态库镜像而非 app 可执行,测试在 # 独立 xctest runner 中运行,宿主 app 不再被启动。 - target: ShadowsocksX-NG2Core - package: AgeSwift ShadowsocksX-NG2SmokeTests: type: bundle.unit-test platform: macOS sources: - path: Tests/Smoke # app 产物定位 helper(与单测 target 共享,见 Tests/Support/AppArtifact.swift)。 # 同样是显式文件路径:移动它必须同步此处,否则 xcodegen 以缺文件失败。 - path: Tests/Support/AppArtifact.swift - path: Tests/Support/ProxyRuntimeFixture+Document.swift settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.SmokeTests # 身份理由同 ShadowsocksX-NG2Tests:随 config 基线,Debug 无证书可跑。 info: path: Tests/Smoke/Info.plist properties: {} dependencies: # 去宿主化(ADR 0021):smoke 同样链 Core 镜像库;真实 sslocal/agent 从 # scheme 构建的 app 产物取(AppArtifact),宿主 app 不再被启动。 - target: ShadowsocksX-NG2Core - package: AgeSwift schemes: # app 主 scheme。测试动作注入 SSXNG_SYSTEM_LOG=0 关闭统一日志写入: # 测试进程与其拉起的子进程不把测试日志灌进系统日志 store(stderr 与 # agent.log 不受影响)。Run/Profile 不带该变量——从 Xcode 调试运行时 # 系统日志照常输出。开关实现见 RuntimeLog.systemLogEnabled。 ShadowsocksX-NG2: build: targets: ShadowsocksX-NG2: [run, test, profile, archive, analyze] run: config: Debug test: config: Debug targets: # 单测多 runner 并行执行:夹具均为进程私有资源(运行时空闲端口、 # UUID 后缀的 UserDefaults suite 与 Keychain service、匿名 XPC # listener、临时目录),跨 runner 无共享状态。 - name: ShadowsocksX-NG2Tests parallelizable: true environmentVariables: - variable: SSXNG_SYSTEM_LOG value: "0" isEnabled: true profile: config: Release analyze: config: Debug archive: config: Release # 真实进程冒烟独立 scheme(task test:smoke):拉起真实 sslocal,按需执行; # 发布门槛要求与 packaging-gate 一起全绿。 ShadowsocksX-NG2Smoke: build: targets: ShadowsocksX-NG2: [run, test] ShadowsocksX-NG2SmokeTests: [test] test: targets: - name: ShadowsocksX-NG2SmokeTests parallelizable: true # 同主 scheme:冒烟进程与其拉起的真实 sslocal/agent 关闭统一日志写入。 environmentVariables: - variable: SSXNG_SYSTEM_LOG value: "0" isEnabled: true