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

Аудит дрейфа настроек сборки Xcode на облачном Mac

Аудит дрейфа настроек сборки Xcode на облачном Mac

Один и тот же коммит может успешно архивироваться на машине разработчика, но на облачном Mac HexVM внезапно получить другую цель развертывания или потерять одну из архитектур. Обычно причина кроется не в исходном коде, а в настройках сборки, которые Xcode вычисляет в итоге. Файл проекта, .xcconfig, переменные окружения и аргументы командной строки последовательно переопределяют друг друга, поэтому проверка только одного файла не позволяет надежно определить фактическую конфигурацию Target.

Такой дрейф не всегда сразу приводит к сбою. Сначала он может лишь изменить артефакт, а проявиться уже на этапе публикации. Более надежный подход — хранить очищенный эталон фактических настроек, а затем заново формировать и сравнивать его перед каждым слиянием и выпускным архивированием.

Сначала определите область аудита настроек

Не сохраняйте весь вывод xcodebuild без обработки. Абсолютные пути, временные каталоги и номера сборок создают множество бессмысленных различий, поэтому сначала разделите настройки на три категории.

Категория Типичные ключи Обработка
Границы артефакта PRODUCT_BUNDLE_IDENTIFIER, SUPPORTED_PLATFORMS, ARCHS Проверять при изменении
Поведение компиляции SWIFT_VERSION, SWIFT_OPTIMIZATION_LEVEL, GCC_PREPROCESSOR_DEFINITIONS Проверять при изменении
Шум конкретного узла BUILD_DIR, TEMP_DIR, PROJECT_TEMP_DIR Удалять или нормализовать

Настройки подписи также следует включить в эталон, однако нельзя сохранять в репозитории файлы сертификатов, содержимое закрытых ключей или временные учетные данные. Цель аудита — имена параметров, режим подписи и границы полномочий, а не копирование конфиденциальных материалов.

Эталон — не файл конфигурации, который «никогда не меняется». Это ожидаемое состояние, требующее проверки кода: изменения допустимы, но они должны быть видимыми, объяснимыми и обратимыми.

Экспортируйте фактические настройки Target

Сначала зафиксируйте проект, Scheme, конфигурацию сборки и целевую платформу. Для проекта на основе workspace замените -project на -workspace, не меняя остальные аргументы.

mkdir -p .ci/build-settings

xcodebuild \
  -project App.xcodeproj \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -showBuildSettings \
  > .ci/build-settings/raw.txt

Перед запуском выполните xcodebuild -list -project App.xcodeproj и убедитесь, что Scheme является общей. Если конвейер использует дополнительные параметры, например флаги функций или пользовательский SYMROOT, при создании эталона и выполнении сборки необходимо передавать один и тот же набор аргументов. В противном случае сравниваться будут два разных контекста.

В проектах с несколькими Target вывод содержит несколько наборов одноименных ключей. Не удаляйте такие повторы без разбора: сохраняйте заголовок Target или создавайте отдельный файл для каждой Scheme. У приложения, расширений и тестовых Target диапазоны развертывания изначально могут различаться.

Удалите шум путей и создайте стабильный эталон

Следующий скрипт извлекает строки формата KEY = VALUE, удаляет ключи временных каталогов и заменяет рабочий и домашний каталоги стабильными маркерами. Скрипт не обрабатывает секретные ключи и не выводит полный набор переменных окружения.

from pathlib import Path
import os

source = Path(".ci/build-settings/raw.txt")
target = Path(".ci/build-settings/current.txt")

ignored = {
    "BUILD_DIR",
    "BUILD_ROOT",
    "CONFIGURATION_BUILD_DIR",
    "DERIVED_FILES_DIR",
    "PROJECT_TEMP_DIR",
    "TARGET_TEMP_DIR",
    "TEMP_DIR"
}

root = str(Path.cwd())
home = str(Path.home())
rows = []

for line in source.read_text().splitlines():
    stripped = line.strip()
    if " = " not in stripped:
        continue
    key, value = stripped.split(" = ", 1)
    if key in ignored:
        continue
    value = value.replace(root, "<ROOT>").replace(home, "<HOME>")
    rows.append(f"{key}={value}")

target.write_text("
".join(sorted(rows)) + "
")

После первой проверки результата скопируйте current.txt в эталонный файл с именем по назначению, например release-ios.txt, и зафиксируйте его в репозитории.

python3 .ci/normalize_build_settings.py
cp .ci/build-settings/current.txt \
   .ci/build-settings/release-ios.txt

Если пути зависимостей по-прежнему часто меняются, сначала определите, влияют ли они на входные данные компиляции. Не фильтруйте HEADER_SEARCH_PATHS, FRAMEWORK_SEARCH_PATHS или OTHER_SWIFT_FLAGS только ради «нулевой разницы»: изменения этих ключей часто и являются проблемой, которую необходимо обнаружить.

Превратите различия в блокирующую проверку конвейера

Проверку следует запускать после разрешения зависимостей, но до выпускного архивирования. Так она получит полные пути поиска и сможет остановить неверную конфигурацию до начала ресурсоемкого этапа архивирования.

set -euo pipefail

xcodebuild \
  -project App.xcodeproj \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -showBuildSettings \
  > .ci/build-settings/raw.txt

python3 .ci/normalize_build_settings.py

diff -u \
  .ci/build-settings/release-ios.txt \
  .ci/build-settings/current.txt

Если diff возвращает ненулевой код, конвейер должен остановиться и сохранить текст различий. Не перезаписывайте эталон новым файлом автоматически, иначе блокирующая проверка превратится в обычный регистратор изменений.

Список разрешенных настроек

Команда может вести явный список игнорирования для заведомо безвредных ключей, но он должен оставаться коротким, а для каждого исключения необходимо указывать причину. Рекомендуется блокировать следующие изменения:

  • IPHONEOS_DEPLOYMENT_TARGET и SUPPORTED_PLATFORMS
  • ARCHS, EXCLUDED_ARCHS и ONLY_ACTIVE_ARCH
  • SWIFT_VERSION и уровень оптимизации
  • CODE_SIGN_STYLE и способ выбора идентификатора подписи
  • PRODUCT_BUNDLE_IDENTIFIER и путь к файлу полномочий
  • DEBUG_INFORMATION_FORMAT и аргументы компоновщика

Разбирайте типичные ложные срабатывания и реальный дрейф

Если при каждом запуске появляются большие блоки различий в путях, сначала проверьте, охватывает ли замена корневого каталога пути после разрешения символических ссылок. В начале задания можно записывать результат pwd -P, а в скрипте нормализовать как логический, так и физический путь.

Если локально различий нет, а в облаке они возникают постоянно, последовательно проверьте следующее: используется ли одна и та же Scheme, совпадает ли имя конфигурации, добавляются ли настройки через командную строку, завершено ли разрешение зависимостей и участвуют ли переменные окружения в подстановках .xcconfig. Не изменяйте эталон первым делом только для того, чтобы подогнать его под результат.

Обнаружив реальное изменение, сначала найдите его источник и только затем обновляйте эталон:

  1. С помощью xcodebuild -showBuildSettings определите, к какому Target относится изменение.
  2. Найдите соответствующий ключ в файле проекта, .xcconfig и параметрах конвейера.
  3. Опишите цель изменения и его влияние на Debug и Release.
  4. Выполните чистую сборку и проверьте архивирование.
  5. Включите изменение конфигурации и обновление эталона в одну проверку кода.

Организуйте устойчивый цикл аудита

Эталоны настроек следует разделять по Scheme, конфигурации сборки и платформе, а не пытаться охватить все сценарии одним файлом. Для обычных коммитов можно проверять эталон Release основного приложения, а проверки расширений, тестовых пакетов и процесса публикации запускать при изменениях в соответствующих областях.

После каждого изменения политики версий Xcode настройки необходимо сформировать заново и проверить по пунктам, поскольку значения по умолчанию могли измениться. Важнее не количество различий, а то, какие из них меняют входные данные компиляции, структуру артефакта, границы подписи и целевую среду выполнения. Если сохранять эту цепочку свидетельств, причину сбоя сборки больше не придется искать во всей машине, а изменения конфигурации можно будет отслеживать так же, как изменения исходного кода.

Часто задаваемые вопросы

Почему недостаточно сравнить файл проекта и xcconfig?

Они показывают только входные данные. Вывод xcodebuild учитывает наследование, условные значения и переопределения командной строки, поэтому точнее отражает реальную конфигурацию.

Какие изменения должны останавливать конвейер?

Обычно следует блокировать изменения цели развертывания, архитектур, версии Swift, оптимизации, режима подписи и идентификатора продукта. Временные пути сначала нормализуют.

Эксклюзивный Apple Silicon

Выберите выделенный облачный Mac для следующей сборки

Выбирайте модель Mac mini, срок аренды и регион под рабочую нагрузку. Каждый заказ соответствует отдельному физическому узлу, а фактическая доступность определяется в реальном времени через консоль.

Выбрать облачный Mac