参与贡献
MacroSIP 采用 GPLv2 或更新版本(PJSIP 为 GPLv2+,ADR 0007);任何贡献都在该许可证下接受,任何捆绑的第三方代码必须与 GPL 兼容。错误和功能请求请提交到 https://github.com/HSMM/MacroSIP/issues。动手改代码之前,请阅读 docs/DEVELOPMENT.md(工程说明)、docs/ROADMAP.md(已交付和即将到来的内容)、docs/GLOSSARY.md(在代码和文档中使用其术语)、docs/adr/ 中的 ADR,以及 UI 规范 docs/design/SCREENS.md 和 docs/design/mockups.html。
工作流程(ADR 0008)
工作按 docs/ROADMAP.md 中的里程碑组织。每个任务从一份书面简报开始:目标、边界、文件、完成标准和已知事实;简报是自成一体的。每个里程碑都由未编写该代码的人独立评审(错误、竞争条件、泄漏、钥匙串和 TLS 处理),然后验证:构建、测试、环回自测以及在真实 SIP 账户上的一次通话。UI 代码只在样稿获批后才开始编写。已确定的决策不会被悄悄推翻:要更改一项决策,请添加新的 ADR(docs/adr/README.md:Context、Decision、Consequences;接受后除“Superseded by”一行外永不编辑)。没有 ADR 不要添加依赖。
针对 main 创建拉取请求;CI 必须在 arm64 和 Intel 两个作业上都通过(见 测试)。在同一个拉取请求中保持 docs/design/SCREENS.md、mockups.html 和 docs/DEVELOPMENT.md 与您更改的代码同步。
编码规则
- Swift 6,严格并发。
MacroSIPCore不导入任何 UI。PJSIP 回调到达于 PJSIP 工作线程:在触碰应用状态之前跳到主 actor,绝不在主 actor 上阻塞 PJSIP 线程。每个可能失败的pjsua_*调用都返回pj_status_t;检查它并通过pj_strerror呈现文本(EngineError.pjsip)。错误绝不导致引擎崩溃。 - 一个产品,一个构建(ADR 0003):bundle id
com.hsmm.macrosip,没有功能构建标志。 - **用
@Local,不用@State。**在当前 SDK 中@State同时也是附加宏,其插件只随 Xcode 提供,因此在命令行工具下无法编译。请使用Sources/MacroSIPApp/Views/Components/Local.swift中的@Local包装器;它是直接替代品。 - **日志。**应用代码通过
AppLog.app、.recording、.presence记录日志(Platform/AppLog.swift:os_log 加轮转文件~/Library/Logs/MacroSIP/MacroSIP.log),绝不直接用Logger。引擎的.log事件落在engine和pjsip类别中。--log-level只设置 PJSIP 的详细程度。 - 密码只放在钥匙串中;其他一切都是 Application Support 中的 JSON(ADR 0004)。持久化结构体中的新字段将缺失的键解码为默认值,因此旧文件仍能加载。
- **字符串。**视图绝不包含用户可见的字面量。每个字符串都通过
Model/Strings.swift中的L,并同时存在于en.lproj和ru.lproj中;两种语言都有了才算完成。见下文“添加字符串”。 - **每次更改都要有测试。**新的引擎行为需要一个 Swift Testing 用例(基于环回或存根,不用真实网络),当它在通话中对用户可见时还需要一个自测步骤;新模型需要值测试;新的 UI 状态需要一个演示状态以便截图。在创建拉取请求之前运行
scripts/test.sh、scripts/selftest-call.sh和scripts/check-strings.sh。
添加字符串
- 向
L添加一个带点分键和英文默认值的static var或func,如现有的static var tabsContacts: String { loc("tabs.contacts", "Contacts") }。插值对文本使用%@,对整数使用%lld;有两个或更多参数时,在两个文件中都使用%1$@、%2$@。 - 将键添加到
Resources/en.lproj/Localizable.strings和Resources/ru.lproj/Localizable.strings。 - 计数(联系人、通话、条目)改放到
Localizable.stringsdict,英语用one/other,俄语用one/few/many/other;同一个键绝不出现在两个文件中。 - 运行
scripts/check-strings.sh。
产品、编解码器和协议名称(SIP、SRTP、TLS、STUN、ICE、DTMF、REGISTER)、诊断报告和日志行不翻译。检查俄语下的布局:dist/MacroSIP.app/Contents/MacOS/MacroSIP --demo-state contacts -AppleLanguages "(ru)"。
添加设置项
- 在
Sources/MacroSIPCore/Model/AppSettings.swift中正确的结构体(GeneralSettings、AudioSettings、RecordingSettings、AutoAnswerSettings、NetworkSettings、CallOptions、CodecOptions、QuickKeysSettings、DirectorySettings)里添加字段,带默认值和宽容的init(from:),并在SettingsCompatTests中添加一个值测试,确认旧的settings.json仍能解码。 - 应用它:引擎侧的选项通过
AppModel+Engine.swift(applyCallOptions、applyCodecOptions、scheduleNetworkApply;需要重新启动的网络字段列在NetworkSettings.needsRestart(from:)中)。 - 使用
model.updateSettings { $0... = ... }将控件添加到Views/Settings/中的面板(每个面板是一个 680 pt 宽的PaneContainer)。 - 在
L和两种语言中添加字符串;在docs/design/SCREENS.md中描述该控件及其状态,如果面板布局变化则更新mockups.html。 - 如果该控件有值得截图的状态,在
Model/DemoState.swift中添加一个演示状态。
添加编解码器
third_party/pjsip/config_site.h:设置PJMEDIA_HAS_<NAME>_CODEC 1(并且,如果 pjproject 的 configure 有--disable-<name>-codec标志,确保scripts/build-pjsip.sh没有传入它;当 configure 禁用了某个预期的编解码器或缺少编解码器归档时,脚本已经会失败)。重新运行scripts/build-pjsip.sh:PJSIP 为两个架构重新构建,OpenSSL 和 Opus 保持缓存。Package.swift:将归档添加到pjsipLibraries,保持与third_party/pjsip/LINKFLAGS同步;然后rm -rf .build和swift build。MacroSIPCore中的Engine.optionalCodecs(显示在 G.711 之后的 id,启用前优先级保持为 0)和Views/Settings/CodecsPane.swift中的CodecCatalog(all、onByDefault;它会追加已存储顺序中缺少的 id)。- 测试:一个 id 列表测试和一个协商该编解码器的环回通话(
Engine262Tests展示了模式),加上自测的“codecs”步骤。
没有 G.729(bcg729 保持禁用);构建见 ADR 0002 和 ADR 0012。
添加目录来源类型
来自 docs/DIRECTORY.md:
- 向
DirectorySourceKind添加 case,如果它有设置,则添加一个带宽容init(from:)(每个键都可选)的Codable配置结构体,以及DirectorySource上一个用try?解码的字段,这样损坏的配置不会丢掉整个设置文件。 - 实现
DirectoryProvider(保有缓存时为actor,否则为struct):search返回最多limit个条目,lookup使用DirectoryNumber.matches,test返回.connected(entries:),allEntries返回用于导入的全部内容。抛出DirectoryError;.signInRequired将来源置于“需要登录”。对于本地列表,构建一个DirectoryIndex。 - 从
DirectoryService.defaultProvider返回它。机密通过DirectoryCredentials,绝不进入settings.json。 - 测试放在
Directory*Tests.swift文件中;DirectoryServiceTests.swift中的StubDirectoryProvider覆盖服务侧。 - UI:
Views/Directory/DirectorySourceSheet.swift中的类型弹出式菜单和字段、字符串、SCREENS.md 第 13 节,以及一个directory-sheet-<kind>演示状态。
提交信息
简明英文,现在时,说明做了什么和为什么。第一行点明领域和更改(Engine: retry REGISTER with backoff after a transport error、Settings: Network pane with the RTP port range);原因不明显时在正文中解释。不加 trailer。每完成一部分就提交,包括随之而来的文档。