Участие
MacroSIP распространяется под GPLv2 или новее (PJSIP — GPLv2+, ADR 0007); любой вклад принимается под этой лицензией, а любой встроенный сторонний код должен быть совместим с GPL. Ошибки и пожелания — на https://github.com/HSMM/MacroSIP/issues. Прежде чем трогать код, прочитайте docs/DEVELOPMENT.md (инженерные заметки), docs/ROADMAP.md (что выпущено и что дальше), docs/GLOSSARY.md (используйте его термины в коде и документации), ADR в docs/adr/ и спецификацию UI docs/design/SCREENS.md вместе с docs/design/mockups.html.
Рабочий процесс (ADR 0008)
Работа организована по вехам из docs/ROADMAP.md. Каждая задача начинается с письменного задания: цель, границы, файлы, критерии готовности и уже известные факты; задания самодостаточны. Каждая веха проходит независимое ревью тем, кто не писал код (ошибки, гонки, утечки, работа со связкой ключей и TLS), затем проверку: сборка, тесты, самопроверка по loopback и звонок на настоящем SIP-аккаунте. Код UI начинается только после утверждения макетов. Принятые решения не пересматриваются молча: чтобы изменить одно из них, добавьте новый ADR (docs/adr/README.md: Context, Decision, Consequences; после принятия не редактируется, кроме строки «Superseded by»). Не добавляйте зависимости без ADR.
Откройте pull request в main; CI должен пройти и в задании arm64, и в задании Intel (см. Тесты). Держите docs/design/SCREENS.md, mockups.html и docs/DEVELOPMENT.md в соответствии с кодом, который вы меняете, в том же pull request.
Правила кода
- Swift 6, строгая конкурентность. В
MacroSIPCoreнет импортов UI. Обратные вызовы PJSIP приходят на рабочих потоках PJSIP: переходите на главный актор, прежде чем трогать состояние приложения, и никогда не блокируйте поток PJSIP ожиданием главного актора. Каждый вызовpjsua_*, который может завершиться ошибкой, возвращаетpj_status_t; проверяйте его и показывайте текст черезpj_strerror(EngineError.pjsip). Ошибки никогда не роняют движок. - Один продукт, одна сборка (ADR 0003): bundle id
com.hsmm.macrosip, без флагов сборки для возможностей. @Local, а не@State. В текущем SDK@State— это ещё и присоединённый макрос, плагин которого поставляется только с Xcode, поэтому под Command Line Tools он не компилируется. Используйте обёртку@LocalизSources/MacroSIPApp/Views/Components/Local.swift; это прямая замена.- Журналирование. Код приложения пишет через
AppLog.app,.recording,.presence(Platform/AppLog.swift: os_log плюс ротируемый файл~/Library/Logs/MacroSIP/MacroSIP.log), никогда через голыйLogger. События.logдвижка попадают в категорииengineиpjsip.--log-levelзадаёт подробность только PJSIP. - Пароли только в связке ключей; всё остальное — JSON в Application Support (ADR 0004). Новые поля в сохраняемых структурах декодируют отсутствующие ключи как значения по умолчанию, чтобы старые файлы продолжали загружаться.
- Строки. Представления никогда не содержат видимых пользователю литералов. Каждая строка идёт через
LвModel/Strings.swiftи существует и вen.lproj, и вru.lproj; строка не готова, пока её нет на обоих языках. См. «Добавить строку» ниже. - Тесты с каждым изменением. Новое поведение движка получает тест Swift Testing (на loopback или заглушке, без настоящей сети) и, если оно видно пользователю в звонке, шаг самопроверки; новые модели — тесты значений; новые состояния UI — демонстрационное состояние, чтобы их можно было снять. Запускайте
scripts/test.sh,scripts/selftest-call.shиscripts/check-strings.shперед открытием pull request.
Добавить строку
- Добавьте
static varилиfuncвLс ключом через точку и английским значением по умолчанию, как у существующего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:)). - Добавьте элемент управления на вкладку в
Views/Settings/(каждая вкладка —PaneContainerшириной 680 pt) черезmodel.updateSettings { $0... = ... }. - Строки в
Lи на обоих языках; опишите элемент и его состояния вdocs/design/SCREENS.mdи обновитеmockups.html, если раскладка вкладки меняется. - Демонстрационное состояние в
Model/DemoState.swift, если у элемента есть состояние, достойное снимка.
Добавить кодек
third_party/pjsip/config_site.h: задайтеPJMEDIA_HAS_<NAME>_CODEC 1(и, если у configure pjproject есть флаг--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.Engine.optionalCodecsвMacroSIPCore(идентификаторы, показываемые после G.711, с приоритетом 0, пока не включены) иCodecCatalogвViews/Settings/CodecsPane.swift(all,onByDefault; он добавляет идентификаторы, которых нет в сохранённом порядке).- Тесты: тест перечисления идентификаторов и loopback-звонок, согласующий кодек (образец —
Engine262Tests), плюс шаг самопроверки «codecs».
Без G.729 (bcg729 остаётся выключенным); сборка описана в ADR 0002 и ADR 0012.
Добавить тип источника справочника
Из docs/DIRECTORY.md:
- Добавьте вариант в
DirectorySourceKindи, если у него есть настройки, структуру конфигурацииCodableс терпимымinit(from:)(каждый ключ необязателен) и поле в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;StubDirectoryProviderвDirectoryServiceTests.swiftпокрывает сторону сервиса. - UI: список типов и поля в
Views/Directory/DirectorySourceSheet.swift, строки, раздел 13 SCREENS.md и демонстрационное состояниеdirectory-sheet-<kind>.
Сообщения коммитов
Простой английский, настоящее время, что и почему. Первая строка называет область и изменение (Engine: retry REGISTER with backoff after a transport error, Settings: Network pane with the RTP port range); тело объясняет причину, когда она не очевидна. Без трейлеров. Коммитьте каждую законченную часть вместе с относящейся к ней документацией.
См. также Устройство, Сборка, Тесты, Релизы.