Инженерное руководство MangoVM

Разделяем среды iOS через xcconfig и проверяем архив

Разделяем среды iOS через xcconfig и проверяем архив

Когда один iOS-проект подключается к сервисам разработки, предварительной и рабочей сред, самая опасная ошибка обычно заключается не в сбое компиляции, а в успешном создании архива для неверной среды. Даже если в релизную сборку попадут адрес сервиса разработки, расширенное журналирование или внутренний функциональный флаг, конвейер всё равно может завершиться успешно. Надёжнее разделить публичные настройки по слоям, зафиксировать точку входа в сборку и сделать проверку архива обязательным этапом выпуска, а не рассчитывать на то, что разработчик выберет правильный пункт меню в Xcode.

Сначала отделите конфигурацию от секретов

xcconfig подходит для параметров сборки, которые допустимо раскрывать вместе с клиентским приложением: базового адреса API, суффикса Bundle ID, уровня журналирования и значений функциональных флагов по умолчанию. Это не хранилище секретов. Любое значение, участвующее в сборке клиентского приложения, может оказаться в Info.plist, ресурсах, параметрах компиляции или исполняемом файле.

Критерий прост: если пользователь не должен узнать значение после получения установочного пакета, не передавайте его Xcode для включения в App.

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

Сначала составьте перечень настроек и для каждой укажите, является ли она публичной, откуда поступает и где должна находиться после архивации. Команда должна проверять именно этот перечень, а не десятки значений, разбросанных по Build Settings.

Создайте проверяемую иерархию xcconfig

Рекомендуется разделить общие параметры, различия между средами и локальные переопределения:

Config/
  Base.xcconfig
  Development.xcconfig
  Staging.xcconfig
  Production.xcconfig
  LocalOverrides.xcconfig.example

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

#include "Base.xcconfig"

APP_ENVIRONMENT = staging
API_BASE_URL = https:/$()/staging-api.invalid
ENABLE_VERBOSE_LOGGING = YES
PRODUCT_BUNDLE_IDENTIFIER = com.example.product.staging

Конструкция $() здесь не позволяет интерпретировать двойную косую черту в URL как начало комментария. Домен в примере намеренно не разрешается и используется только в документации; в реальном проекте замените его адресом своего сервиса.

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

#include? "LocalOverrides.xcconfig"

Однако локальные переопределения допустимы только для удобства разработки и не должны становиться неявным входным параметром официальной архивации. Конфигурация рабочей среды должна полностью разрешаться даже без этого файла. Свяжите Configuration Development, Staging и Release с соответствующими файлами и убедитесь, что используемая в CI Scheme отмечена как Shared.

Зафиксируйте точку входа в сборку из командной строки

Задача архивации на облачном Mac не должна наследовать состояние, оставшееся после работы через графический интерфейс. Workspace, scheme, configuration и путь вывода необходимо передавать явно:

set -euo pipefail

WORKSPACE="App.xcworkspace"
SCHEME="App"
CONFIGURATION="Release"
ARCHIVE_PATH="$PWD/build/App.xcarchive"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration "$CONFIGURATION" \
  -showBuildSettings > build-settings.txt

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration "$CONFIGURATION" \
  -archivePath "$ARCHIVE_PATH" \
  clean archive

Проверяйте разрешённые настройки до архивации

Проверяйте не только исходные файлы, но и настройки, которые в итоге разрешил Xcode. Ключевые параметры можно извлечь из build-settings.txt, добавив строгие проверки для рабочей среды:

grep -E "APP_ENVIRONMENT|API_BASE_URL|PRODUCT_BUNDLE_IDENTIFIER|ENABLE_VERBOSE_LOGGING" \
  build-settings.txt

grep -q "APP_ENVIRONMENT = production" build-settings.txt
grep -q "ENABLE_VERBOSE_LOGGING = NO" build-settings.txt

Если в проекте несколько Target, проверяйте каждый отдельно: расширения, тестовые пакеты и основное App могут наследовать разные Base Configuration. Скрипт также должен выводить текущий коммит Git, версию Xcode и выбранную Scheme, чтобы при расхождениях можно было воспроизвести исходные условия.

Сканируйте фактически передаваемый архив

Корректные Build Settings ещё не гарантируют корректность артефакта. Этапы Run Script, генерации кода или копирования ресурсов всё ещё могут перенести тестовую конфигурацию в архив. После архивации как минимум проверьте список свойств основного App, встроенные ресурсы и строки исполняемого файла.

APP_PATH="$(find build/App.xcarchive/Products/Applications -maxdepth 1 -name '*.app' -print -quit)"
test -n "$APP_PATH"

plutil -p "$APP_PATH/Info.plist"

if grep -RInaE "staging-api|debug-token|INTERNAL_ONLY" "$APP_PATH"; then
  echo "forbidden marker found in archive" >&2
  exit 1
fi

EXECUTABLE_NAME=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$APP_PATH/Info.plist")
if strings "$APP_PATH/$EXECUTABLE_NAME" | grep -E "staging-api|debug-token|INTERNAL_ONLY"; then
  echo "forbidden marker found in executable" >&2
  exit 1
fi

Для сканирования используйте маркеры, определённые командой, и не записывайте реальные секреты непосредственно в скрипты или журналы. Рекомендуется вести два коротких списка — разрешённых и запрещённых значений, — чтобы обычные слова не вызывали ложных срабатываний. В результатах сканирования сохраняйте только путь к файлу и название правила, не выводя полное найденное конфиденциальное содержимое.

Объект проверки Что проверять Действие при ошибке
Итоговые Build Settings Название среды, Bundle ID, переключатель журналирования Немедленно остановить архивацию
Info.plist Адрес сервиса, URL Scheme, метка среды Запретить экспорт
Каталог ресурсов App Отладочная конфигурация, тестовые данные, временные файлы Удалить источник и пересобрать
Исполняемый файл Признаки внутренних адресов, маркеры токенов Найти этап генерации и заменить связанные учётные данные

Устраняйте типичные расхождения и заблуждения

Чаще всего расхождение возникает, когда Scheme существует локально, но не опубликована как Shared. Если в репозитории нет соответствующих данных xcshareddata, CI может не найти Scheme либо инженер временно выберет другую точку входа. Вторая распространённая проблема — ветвление в скриптах по названию среды без использования Configuration как единственного источника истины. Со временем это приводит к появлению двух независимых наборов условий.

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

При выполнении этого процесса на выделенных физических узлах MangoVM Runner также должен использовать чистый рабочий каталог и не переиспользовать DerivedData, временные конфигурации и каталоги экспорта из предыдущего задания. Очистку следует ограничить текущим рабочим пространством, чтобы параллельные задачи не удаляли файлы друг друга.

Превратите проверки в обязательные условия слияния и выпуска

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

Перед отправкой изменений воспользуйтесь итоговым списком:

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

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

Можно ли хранить секретный API-ключ в xcconfig?

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

Почему CI собирает не ту среду, что локальный Mac?

Обычно Scheme не опубликована для команды, Configuration сопоставлена иначе либо переменная доступна только в интерактивной оболочке. Передавайте workspace, scheme и configuration явно и сохраняйте вывод showBuildSettings.

Выделенный физический узел

Настройте выделенный облачный Mac для конвейера разработки

MangoVM предлагает физические узлы Apple Silicon в двух конфигурациях: M4 и M4 Pro. Выберите срок аренды, регион и дополнительные параметры хранилища, чтобы проверить полную информацию о заказе.

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