Storyboard может без проблем открываться на машине разработчика, но после слияния изменений удалённая задача архивирования завершается ошибкой уже в середине процесса. Повторная загрузка зависимостей, запуск симулятора или очистка DerivedData здесь не помогут: реальной причиной может быть недействительный outlet, неверно указанный модуль пользовательского класса или атрибут XIB, который не поддерживается текущей целью развёртывания. Для непрерывной интеграции на облачном Mac экономичнее сначала отдельно скомпилировать интерфейсные ресурсы с помощью ibtool и только затем решать, запускать ли полную сборку.
Перенесите проверку интерфейсных ресурсов в начало сборки
ibtool входит в состав Xcode, поэтому его следует вызывать через xcrun, а не указывать в скрипте жёстко заданный путь к инструменту. Он читает Storyboard и XIB, выводит errors, warnings и notices, а также компилирует исходные файлы в storyboardc или nib. На этом этапе не требуется запускать симулятор, поэтому проверку удобно выполнять после разрешения зависимостей, но до xcodebuild archive.
Сначала проверьте, какой каталог разработчика фактически используется на исполняющем узле:
set -euo pipefail
xcode-select -p
xcrun --find ibtool
xcodebuild -version
xcrun ibtool --version
Эти сведения следует записывать в журнал задачи. Если команда хранит несколько версий Xcode на одном облачном Mac, конвейер должен явно задавать DEVELOPER_DIR и удалять эту переменную окружения после завершения задачи, чтобы последующие задания не унаследовали неверный набор инструментов.
Возможность открыть интерфейсный файл в графическом редакторе не означает, что его удастся скомпилировать с указанной версией Xcode, целью развёртывания и контекстом модуля. Задача проверки в CI — воспроизвести набор инструментов выпуска, а не состояние рабочего стола конкретного разработчика.
Сканирование и отдельная компиляция Storyboard и XIB
Следующий скрипт сканирует репозиторий в поисках интерфейсных файлов и создаёт отдельный каталог вывода для каждого входного файла. Важно исключить каталоги сборки и зависимостей, иначе проверка может повторно обработать сгенерированные файлы или сторонние ресурсы.
#!/usr/bin/env bash
set -euo pipefail
root="${1:-$PWD}"
target="${IPHONEOS_DEPLOYMENT_TARGET:-16.0}"
work="${TMPDIR:-/tmp}/ibtool-preflight"
log="$work/ibtool.log"
rm -rf "$work"
mkdir -p "$work/out"
: > "$log"
find "$root" \
\( -path "*/DerivedData/*" -o -path "*/build/*" -o -path "*/Pods/*" \) -prune \
-o \( -name "*.storyboard" -o -name "*.xib" \) -print0 |
while IFS= read -r -d '' source; do
relative="${source#"$root"/}"
safe_name="$(printf '%s' "$relative" | tr '/ ' '__')"
case "$source" in
*.storyboard)
output="$work/out/${safe_name%.storyboard}.storyboardc"
;;
*.xib)
output="$work/out/${safe_name%.xib}.nib"
;;
esac
printf 'Checking %s
' "$relative" | tee -a "$log"
xcrun ibtool \
--errors \
--warnings \
--notices \
--minimum-deployment-target "$target" \
--compile "$output" "$source" 2>&1 | tee -a "$log"
done
Скрипт использует временный каталог и не засоряет репозиторий. При сбое файл ibtool.log следует сохранить; при успешном выполнении результаты компиляции можно удалить, загрузив только сводку журнала. Не добавляйте временные результаты в систему контроля версий и не позволяйте нескольким параллельным задачам использовать один фиксированный каталог.
Учитывайте границы между отдельной предварительной проверкой и сборкой проекта
Отдельный вызов ibtool позволяет обнаружить повреждение XML, некоторые проблемы с соединениями, несовместимые атрибуты и ошибки компиляции, но не учитывает все настройки сборки полного target. Проверку модуля пользовательского контроллера представления, включения ресурса в Copy Bundle Resources и наличия класса после условной компиляции по-прежнему необходимо выполнять через xcodebuild.
Рекомендуется разделить проверку на два уровня:
| Уровень | Входные данные | Основные обнаруживаемые проблемы | Действие при сбое |
|---|---|---|---|
| Быстрая предварительная проверка | Storyboard, XIB | Повреждение файлов, несовместимые атрибуты, базовые ошибки соединений | Немедленно остановить процесс |
| Сборка проекта | workspace, project, Scheme | Разрешение модулей, принадлежность ресурсов, контекст компоновки и подписи | Сохранить полный журнал сборки |
Если отдельная предварительная проверка прошла успешно, а сборка проекта завершилась с ошибкой, сначала проверьте Target Membership, поле Module, историю переименования классов и наследование цели развёртывания. Если же предварительная проверка уже завершилась ошибкой, продолжать более длительное архивирование не имеет смысла.
Включите локализованные файлы в тот же контрольный этап
Storyboard обычно локализуют одним из двух способов: поддерживают отдельные ресурсы для каждого языка либо используют Base Internationalization вместе с .strings. В первом случае легко получить расхождение идентификаторов объектов, а во втором — оставить уже удалённые ключи. Сначала проверьте базовый формат .strings с помощью plutil:
find . -name "*.strings" -print0 |
while IFS= read -r -d '' file; do
plutil -lint "$file"
done
Для Base Storyboard можно создать текущий набор ключей и сравнить его с локализованными файлами в репозитории:
mkdir -p "${TMPDIR:-/tmp}/ibtool-strings"
xcrun ibtool \
--generate-strings-file "${TMPDIR:-/tmp}/ibtool-strings/Main.strings" \
"App/Base.lproj/Main.storyboard"
plutil -lint "${TMPDIR:-/tmp}/ibtool-strings/Main.strings"
Сгенерированный файл предназначен только для сравнения и не должен напрямую заменять переводы. Надёжнее извлечь имена ключей, отсортировать их и проверить различия: новые ключи добавить в список на перевод, для удалённых создать уведомление об очистке, а тексты существующих ключей по-прежнему вести в рамках процесса локализации. Такой подход выявляет пропуски и при этом не заменяет имеющиеся переводы текстом из Base.
Управляйте базовой линией предупреждений и типичными ложными срабатываниями
Правило, при котором любой вывод считается ошибкой, поначалу выглядит строгим, но на практике приучает команду игнорировать красные индикаторы. Более жизнеспособный подход: немедленно завершать процесс при errors; сравнивать warnings с проверенной базовой линией в репозитории и блокировать только новые предупреждения; сохранять notices в артефактах сборки и периодически очищать их.
Рекомендуемый порядок диагностики:
- Убедитесь, что
DEVELOPER_DIRсовпадает со значением полной сборки. - Проверьте, что
IPHONEOS_DEPLOYMENT_TARGETполучен из фактических настроек проекта. - Проверьте регистр символов в путях к файлам, особенно для ресурсов, при переименовании которых изменился только регистр букв.
- Найдите недействительные outlet, action и имена пользовательских классов.
- Убедитесь, что временные каталоги изолированы для каждой задачи и параллельные компиляции не перезаписывают результаты друг друга.
- Выполните ещё одну сборку без подписи с тем же Scheme, чтобы проверить контекст модулей.
В базовой линии предупреждений следует хранить стабильные идентификаторы, а не временные абсолютные пути. При обновлении Xcode сначала запустите предварительную проверку в отдельной ветке, изучите новые диагностические сообщения и только затем обновите базовую линию. Не отфильтровывайте все новые предупреждения непосредственно в коммите с обновлением.
Внедряйте проверку поэтапно с возможностью отката
При первом подключении можно в течение недели только собирать журналы, не блокируя слияния. После определения источников ложных срабатываний настройте сбой для детерминированных errors, а затем постепенно включите контроль новых warnings. Сам скрипт следует хранить в репозитории и использовать как на локальных, так и на облачных Mac, чтобы в конфигурации CI не скрывалась другая реализация.
Итоговый список проверок: набор инструментов зафиксирован; для сканируемых каталогов явно заданы исключения; каждая задача использует отдельный временный каталог; цель развёртывания берётся из конфигурации проекта; журнал сохраняется как вложение при сбое; локализованные ключи только сравниваются, но не перезаписываются; полная сборка Scheme по-прежнему выполняется. В результате проблемы с интерфейсными ресурсами обнаруживаются за десятки секунд на этапе предварительной проверки, а не в конце архивирования в виде ошибки, которую трудно воспроизвести.
Часто задаваемые вопросы
Заменяет ли проверка ibtool полную сборку Xcode?
Нет. Она быстро проверяет структуру и компилируемость интерфейсных ресурсов, но итоговый Scheme всё равно должен пройти сборку и тесты.
Почему Storyboard открывается локально, но не проходит CI?
Обычно различаются версия Xcode, целевая версия системы, регистр пути или доступность модуля с пользовательским классом.
Следует ли считать каждый notice ошибкой?
Нет. Сначала блокируйте только errors, фиксируйте warnings относительно базового списка, а notices сохраняйте для анализа.
Запустите следующую задачу на выделенном физическом узле
Выберите узел и период оплаты, затем начните развертывание с фиксированной конфигурацией M4, 16GB RAM и 256GB SSD. Фактическая доступность отображается в консоли в реальном времени.