Когда несколько разработчиков одновременно изменяют проект Xcode, наиболее опасны не ошибки компиляции, а повреждения project.pbxproj, которые не мешают текстовому слиянию. Ссылка на файл может быть удалена, Scheme — не опубликована для общего доступа, а критически важные настройки сборки — незаметно изменены через интерфейс. При этом проблема может обнаружиться только на этапе архивации. Надёжнее считать файл проекта отдельным входом сборки на облачном Mac: сначала выполнять сравнительно недорогую проверку целостности и лишь затем запускать разрешение зависимостей, тестирование и архивацию.
Что должна проверять система контроля
Практичная система контроля должна охватывать как минимум четыре уровня, причём порядок важен: сначала проверяются текстовые артефакты, затем формат файла, после этого Xcode должен фактически разобрать проект, а в конце сравниваются ключевые настройки. Если проверка не пройдена на одном из уровней, процесс следует сразу остановить, не расходуя время на дальнейшую сборку.
| Уровень | Объект проверки | Что означает ошибка |
|---|---|---|
| Текст | Маркеры конфликтов, пустой файл | Слияние ещё не завершено |
| Структура | project.pbxproj |
Структуру plist невозможно разобрать |
| Проект | Project, Target, Scheme | Xcode не может построить модель проекта |
| Конфигурация | SDK, версия развёртывания, способ подписания | Настройки непредвиденно изменились |
Отсутствие конфликтов в
git diffне означает, что проект Xcode корректен. Система контроля версий подтверждает лишь завершение текстового слияния и не знает, распознаёт ли Xcode ссылки на объекты, Target или Scheme.
Скрипт проверки должен выполняться из фиксированного каталога и использовать заданный путь к Xcode, а не зависеть от текущего состояния интерактивной Shell. Если проект содержит одновременно .xcodeproj и .xcworkspace, повседневные сборки следует проверять через фактически используемую точку входа, но базовый файл project.pbxproj всё равно необходимо проверять отдельно.
Сначала исключите остатки слияния и ошибки формата
Приведённый ниже скрипт можно сохранить как ci/check_xcode_project.sh. В примере предполагается, что проект называется App.xcodeproj. В реальной среде имя следует переопределять переменной окружения, чтобы оно не дублировалось в нескольких конфигурациях CI.
#!/bin/bash
set -euo pipefail
PROJECT_PATH="${PROJECT_PATH:-App.xcodeproj}"
PBXPROJ="${PROJECT_PATH}/project.pbxproj"
test -s "$PBXPROJ" || {
echo "project.pbxproj is missing or empty"
exit 1
}
if grep -nE '^(<<<<<<<|=======|>>>>>>>)' "$PBXPROJ"; then
echo "merge conflict markers found"
exit 1
fi
plutil -lint "$PBXPROJ"
xcodebuild -list -json -project "$PROJECT_PATH" > /tmp/xcode-project-list.json
plutil -lint /tmp/xcode-project-list.json
Маркеры конфликтов ищутся только в начале строки, чтобы случайная последовательность знаков равенства в имени рабочего файла или комментарии не вызывала ложного срабатывания. После успешной проверки plutil запускается xcodebuild -list, поскольку только эта команда строит модель проекта Xcode. Если команда завершается с ненулевым кодом, необходимо сохранить стандартный поток ошибок, а не пытаться автоматически «исправить» или заново сгенерировать файл проекта.
Обработка проектов Workspace
При использовании Workspace добавьте отдельную проверку точки входа:
WORKSPACE_PATH="${WORKSPACE_PATH:-App.xcworkspace}"
xcodebuild -list -json -workspace "$WORKSPACE_PATH" \
> /tmp/xcode-workspace-list.json
plutil -lint /tmp/xcode-workspace-list.json
Не ограничивайтесь проверкой Workspace, пропуская базовый проект. То, что Workspace распознаётся, ещё не гарантирует отсутствие остатков слияния во всех входящих в него файлах проектов.
Проверка общей Scheme и набора целей
Scheme, необходимая для CI, должна храниться в системе контроля версий. Сначала можно проверить наличие общего файла Scheme, а затем подтвердить её имя в JSON-выводе xcodebuild -list. Для разбора достаточно входящего в систему Python, поэтому устанавливать дополнительные зависимости не требуется.
SCHEME_NAME="${SCHEME_NAME:-App}"
SCHEME_FILE="${PROJECT_PATH}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme"
test -s "$SCHEME_FILE" || {
echo "shared scheme is missing: $SCHEME_NAME"
exit 1
}
python3 - "$SCHEME_NAME" /tmp/xcode-project-list.json <<'PY'
import json
import sys
expected = sys.argv[1]
path = sys.argv[2]
with open(path, encoding="utf-8") as handle:
payload = json.load(handle)
schemes = payload.get("project", {}).get("schemes", [])
if expected not in schemes:
raise SystemExit(f"expected scheme not found: {expected}")
PY
Если репозиторий содержит несколько приложений или расширений, следует поддерживать явный список Scheme, а не соглашаться с условием «существует хотя бы одна Scheme». Также не используйте в CI Scheme из личного каталога разработчика: после перехода на другой физический узел эти необщие файлы будут отсутствовать.
Создание снимка ключевых настроек сборки
Даже если проект успешно разобран, его конфигурация может быть изменена по ошибке. В снимок рекомендуется включать только поля, влияющие на смысл артефакта, например PRODUCT_BUNDLE_IDENTIFIER, IPHONEOS_DEPLOYMENT_TARGET, SWIFT_VERSION, CODE_SIGN_STYLE и SUPPORTED_PLATFORMS. Не сохраняйте полный вывод -showBuildSettings: он содержит пути и временные каталоги, из-за чего сравнение между узлами создаст много шума.
xcodebuild -project "$PROJECT_PATH" \
-scheme "$SCHEME_NAME" \
-configuration Release \
-showBuildSettings |
awk -F ' = ' '
/PRODUCT_BUNDLE_IDENTIFIER =/ ||
/IPHONEOS_DEPLOYMENT_TARGET =/ ||
/SWIFT_VERSION =/ ||
/CODE_SIGN_STYLE =/ ||
/SUPPORTED_PLATFORMS =/ {
gsub(/^[ ]+/, "", $1)
print $1 " = " $2
}
' | LC_ALL=C sort > /tmp/build-settings.current
diff -u ci/build-settings.release /tmp/build-settings.current
После первоначального создания ci/build-settings.release файл следует добавить в систему контроля версий. При намеренном изменении версии развёртывания или способа подписания сначала проверьте различия в настройках, а затем обновите снимок в том же изменении. Нельзя разрешать CI автоматически перезаписывать базовый файл, иначе любое отклонение будет принято за новый стандарт.
Интеграция в конвейер и устранение ложных срабатываний
Проверку целостности следует выполнять до загрузки зависимостей и полной сборки, а этапы с ошибками должны иметь понятные названия. Рекомендуемый порядок: получить код, выбрать фиксированную версию Xcode, запустить проверку проекта, разрешить зависимости, скомпилировать, протестировать и создать архив. Локально и на облачном Mac скрипт должен запускаться через одну и ту же точку входа, например:
DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" \
PROJECT_PATH="App.xcodeproj" \
SCHEME_NAME="App" \
bash ci/check_xcode_project.sh
Ложные срабатывания чаще всего возникают по трём причинам. Во-первых, разработчик изменил Scheme, но не сделал её общей. Решение — зафиксировать xcshareddata/xcschemes в репозитории, а не создавать Scheme вручную на узле. Во-вторых, снимок содержит абсолютные пути; в этом случае следует сократить набор полей. В-третьих, разные задания используют разные пути к Xcode. Перед началом проверки нужно вывести и сверить xcodebuild -version, а также зафиксировать DEVELOPER_DIR.
При ошибке проверки в задаче или журнале сборки должны как минимум сохраняться идентификатор коммита, версия Xcode, завершившаяся с ошибкой команда, стандартный поток ошибок и точка входа проекта. Не загружайте полный набор переменных окружения, если он содержит конфиденциальные значения. Если необходимо сменить физический узел, сначала подтвердите доступные конфигурации в панели управления, а затем заново запустите проверку на новом узле из того же репозитория. Не копируйте временное состояние проекта со старого узла.
Контрольный список перед слиянием
Перед добавлением проверки убедитесь в следующем: в скрипте включён set -euo pipefail; путь к проекту и Scheme можно переопределить переменными окружения; поиск маркеров конфликтов выполняется только в файле проекта; Project и Workspace разбираются отдельно в соответствии с фактической точкой входа; общая Scheme добавлена в систему контроля версий; снимок настроек содержит только стабильные поля; каждое обновление базового файла проходит ручную проверку.
Эта проверка не заменяет компиляцию и тестирование, однако позволяет перенести целый класс проблем с этапа архивации на проверку, занимающую лишь несколько секунд. Когда файл проекта становится явным, проверяемым и воспроизводимым входом, команде больше не приходится оценивать состояние основной ветки по принципу «у кого-то из разработчиков проект всё ещё открывается локально».
Часто задаваемые вопросы
Достаточно ли команды plutil для полной проверки проекта Xcode?
Нет. plutil проверяет главным образом структуру и синтаксис. Дополнительно запустите xcodebuild -list, чтобы подтвердить разбор проекта и наличие всех общих схем.
Когда запускать проверку целостности в CI?
Запускайте её перед полной сборкой для каждого изменения и повторно в общей ветке. Так повреждённый проект не будет занимать время архивации.
Что делать с намеренным изменением настроек сборки?
Сначала проверьте разницу, затем явно обновите эталонный снимок в системе контроля версий. Автоматическое обновление эталона скроет случайный дрейф.
Запустите следующую сборку на выделенном физическом узле.
Выберите чип, объем памяти, хранилище, узел и срок аренды и получите облачный Mac с ресурсами, не разделяемыми с другими клиентами.