Когда один 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, временные конфигурации и каталоги экспорта из предыдущего задания. Очистку следует ограничить текущим рабочим пространством, чтобы параллельные задачи не удаляли файлы друг друга.
Превратите проверки в обязательные условия слияния и выпуска
Итоговый процесс можно разделить на два этапа: на этапе запроса на слияние разрешать настройки и проверять структуру конфигурационных файлов, а на этапе выпуска выполнять полную архивацию и сканирование артефакта. Первый этап обеспечивает быструю обратную связь, второй проверяет фактически передаваемый продукт. Любая неудачная проверка среды должна завершаться с ненулевым кодом, а не ограничиваться сообщением в журнале.
Перед отправкой изменений воспользуйтесь итоговым списком:
- Scheme опубликована как Shared, а соответствие Configuration и xcconfig контролируется системой версий;
- для архива рабочей среды явно указаны workspace, scheme и configuration;
- название среды, Bundle ID и переключатель журналирования в
showBuildSettingsсоответствуют ожиданиям; - для основного App и всех расширений выполнено сканирование списков свойств и строк;
- журналы CI не выводят полные значения переменных;
- временные файлы переопределений удаляются после завершения задания;
- после обнаружения утечки сначала заменяются учётные данные, а затем повторно создаётся и проверяется архив.
Когда источник конфигурации, разрешённые настройки и итоговый артефакт образуют три уровня доказательств, выбор среды больше не зависит от привычек разработчиков. Ошибка выпуска будет обнаружена до того, как архив покинет конвейер, а не пользователями вместо команды.
Часто задаваемые вопросы
Можно ли хранить секретный API-ключ в xcconfig?
Нет. Значение может попасть в Info.plist, параметры компиляции, ресурсы или исполняемый файл. Архив приложения следует считать публичным, поэтому привилегированные ключи должны оставаться на сервере.
Почему CI собирает не ту среду, что локальный Mac?
Обычно Scheme не опубликована для команды, Configuration сопоставлена иначе либо переменная доступна только в интерактивной оболочке. Передавайте workspace, scheme и configuration явно и сохраняйте вывод showBuildSettings.
Настройте выделенный облачный Mac для конвейера разработки
MangoVM предлагает физические узлы Apple Silicon в двух конфигурациях: M4 и M4 Pro. Выберите срок аренды, регион и дополнительные параметры хранилища, чтобы проверить полную информацию о заказе.