Инженерные практики

Проверка целостности проекта Xcode на облачном Mac

Проверка целостности проекта Xcode на облачном Mac

Когда несколько разработчиков одновременно изменяют проект 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 mini

Запустите следующую сборку на выделенном физическом узле.

Выберите чип, объем памяти, хранилище, узел и срок аренды и получите облачный Mac с ресурсами, не разделяемыми с другими клиентами.

Выбрать конфигурацию и арендовать