В проекте Xcode, который поддерживается много лет, нередко накапливаются десятки предупреждений компилятора. Если сразу включить SWIFT_TREAT_WARNINGS_AS_ERRORS, основная ветка перестанет собираться. Но если ничего не предпринимать, новые предупреждения затеряются среди старого шума. Более практичный подход — зафиксировать на облачном Mac проверенный базовый список предупреждений и настроить CI так, чтобы он отклонял только новые записи, появившиеся в текущем изменении.
Сначала определите границы проверки
Барьер для предупреждений — это не простой подсчёт строк с warning: в журнале. Загрузка зависимостей, скрипты сборки и системные инструменты могут выводить такие же строки. Прямой подсчёт нестабилен и не помогает разработчику понять, какой файл нужно исправить.
Рекомендуется приводить каждое предупреждение к виду «путь относительно корня репозитория + текст предупреждения», удаляя номера строк и столбцов, временные каталоги и временные метки. Тогда перемещение кода внутри файла не создаст новую сигнатуру, а переименование файла или изменение текста предупреждения по-прежнему потребует проверки.
| Содержимое | Включать в сигнатуру | Причина |
|---|---|---|
| Путь относительно репозитория | Да | Определяет ответственный файл |
| Текст предупреждения | Да | Различает типы проблем |
| Номер строки и столбца | Нет | Легко меняется после редактирования кода |
| Путь DerivedData | Нет | Может отличаться в каждом задании |
| Предупреждения внешних зависимостей | По умолчанию нет | Команда обычно не может исправить их напрямую |
Базовый список означает «известный и принятый на текущий момент технический долг», а не подтверждает корректность этих предупреждений. При удалении каждого старого предупреждения базовый список также следует сокращать.
Создание воспроизводимых сигнатур предупреждений
Сначала зафиксируйте рабочее пространство, Scheme, конфигурацию и SDK. Если на компьютере разработчика используется Debug, а в CI — Release, пути условной компиляции будут различаться, поэтому сравнивать полученные списки не имеет смысла. Приведённый ниже скрипт требует явно передать рабочее пространство и Scheme, а также сохраняет полный вывод сборки.
#!/bin/bash
set -u
WORKSPACE="${WORKSPACE:?set WORKSPACE}"
SCHEME="${SCHEME:?set SCHEME}"
ROOT="$(git rev-parse --show-toplevel)"
OUT="$ROOT/.ci-artifacts"
DERIVED="$OUT/DerivedData"
mkdir -p "$OUT"
rm -rf "$DERIVED"
set +e
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-sdk iphoneos \
-derivedDataPath "$DERIVED" \
CODE_SIGNING_ALLOWED=NO \
build 2>&1 | tee "$OUT/xcodebuild.log"
BUILD_STATUS=${PIPESTATUS[0]}
set -e
grep -F "$ROOT/" "$OUT/xcodebuild.log" \
| grep " warning: " \
| sed "s#${ROOT}/##" \
| sed -E 's#:[0-9]+:[0-9]+: warning: # | #' \
| LC_ALL=C sort -u \
> "$OUT/warnings.current"
exit "$BUILD_STATUS"
CODE_SIGNING_ALLOWED=NO подходит для заданий, которые проверяют только компиляцию. Не следует без изменений использовать его в конвейерах, где требуется архивирование или проверка подписи. Ошибку сборки и появление новых предупреждений также нужно обрабатывать отдельно: если завершается с ошибкой сам xcodebuild, прежде всего следует вернуть его код завершения, а не скрывать реальную проблему пустым файлом предупреждений.
Обработка вывода скриптов и многострочной диагностики
Некоторые этапы Run Script выводят собственные предупреждения, формат которых не обязательно содержит координаты исходного кода. Если эти скрипты поддерживает команда, можно назначить им фиксированный префикс и добавить второе правило разбора. Не используйте одно слишком широкое регулярное выражение для всего вывода, иначе в базовый список попадут сообщения о сети и уведомления об обновлениях инструментов.
Дополнительные диагностические строки Swift обычно не содержат warning: и не подходят в качестве самостоятельных сигнатур, однако их следует сохранять в полном журнале. Барьер нужен для быстрой проверки, а полный журнал — для восстановления контекста; одно не заменяет другое.
Проверка и фиксация первой версии базового списка
Автоматическое задание не должно сразу копировать впервые созданный файл warnings.current в базовый список. Сначала распределите записи по ответственным на основании путей, удалите дубликаты и убедитесь, что в список не попали производные файлы, исходный код зависимостей или временные каталоги. После проверки выполните:
mkdir -p .ci
LC_ALL=C sort -u .ci-artifacts/warnings.current > .ci/xcode-warnings.baseline
git add .ci/xcode-warnings.baseline
git commit -m "Add reviewed Xcode warning baseline"
Базовый список необходимо версионировать вместе с кодом. Любой коммит, расширяющий его, должен показывать добавленные сигнатуры и объяснять причину. Нельзя позволять завершившемуся с ошибкой заданию автоматически «обучаться» новым предупреждениям, иначе барьер будет самостоятельно пропускать их именно тогда, когда должен сработать.
При обновлении Xcode формулировки диагностики компилятора могут измениться. Правильный процесс — выполнить полную сборку в отдельной ветке, отделить действительно новые проблемы от изменений текста, а затем единовременно обновить базовый список и зафиксировать версию набора инструментов. Не следует вместо этого добавлять в скрипт сравнения бесконечно расширяемые нечёткие правила.
Блокировка только новых предупреждений в CI
После сортировки текущих результатов и базового списка команда comm позволяет найти сигнатуры, присутствующие только в текущей сборке:
BASELINE=".ci/xcode-warnings.baseline"
CURRENT=".ci-artifacts/warnings.current"
NEW=".ci-artifacts/warnings.new"
test -f "$BASELINE"
LC_ALL=C sort -u "$BASELINE" -o "$BASELINE"
LC_ALL=C sort -u "$CURRENT" -o "$CURRENT"
LC_ALL=C comm -13 "$BASELINE" "$CURRENT" > "$NEW"
if test -s "$NEW"; then
printf '%s
' "New Xcode warnings detected:"
cat "$NEW"
exit 42
fi
Файлы xcodebuild.log, warnings.current и warnings.new следует архивировать вместе. Код завершения 42 — лишь внутреннее соглашение команды; главное, чтобы «ошибка компиляции» и «новые предупреждения» отображались как разные причины. Получив путь и текст предупреждения, разработчик сможет устранить проблему за один цикл обратной связи.
Если несколько заданий параллельно собирают разные Target, результаты нужно создавать отдельно, а затем объединять, сортировать и удалять дубликаты. Не позволяйте нескольким процессам одновременно записывать данные в один файл: усечение и перемешивание записей приведут к нерегулярным ложным срабатываниям.
Постепенное сокращение базового списка вместо его бессрочной заморозки
После запуска барьера при каждом исправлении старого предупреждения нужно удалять соответствующую строку из базового списка. Можно добавить неблокирующую проверку, которая находит записи, оставшиеся в базовом списке, но уже отсутствующие в текущей сборке, и напоминает автору коммита удалить их. Так список будет только сокращаться и не превратится в непонятный исторический перечень.
До перехода к стабильной эксплуатации следует проверить ещё четыре пункта: используется ли в CI и локально одна версия Xcode; включает ли Scheme ожидаемые Target; влияют ли региональные настройки на вывод инструментов; охватывает ли фильтрация путей репозитория весь собственный исходный код. Рабочий каталог на узле OnceMac также должен каждый раз создаваться заданием в фиксированном месте, чтобы не переиспользовать журналы и DerivedData от предыдущей сборки.
Когда базовый список сократится до нуля, сравнение различий можно удалить и включить политику компилятора, при которой предупреждения считаются ошибками. До этого момента барьер на основе базового списка служит практичным переходным решением: он не скрывает накопленный долг и одновременно не позволяет ему расти.
Часто задаваемые вопросы
Почему не считать все предупреждения ошибками сразу?
При большом старом списке начнут падать все сборки. Базовый список сначала блокирует только новые предупреждения, а строгий режим можно включить после постепенной очистки.
Изменение номера строки вызовет ложное срабатывание?
Да, если номер входит в ключ. Перед сравнением нужно удалить номера строк и столбцов, оставив относительный путь файла и текст предупреждения.
Нужно ли учитывать предупреждения зависимостей?
Обычно нет. Следует фильтровать только исходники, которые поддерживает команда, а внутренние зависимости добавлять отдельными явными правилами путей.
Запустите следующую сборку на выделенном физическом узле.
Выберите чип, объем памяти, хранилище, узел и срок аренды и получите облачный Mac с ресурсами, не разделяемыми с другими клиентами.