Как найти скрытые локальные зависимости чистой сборкой в Cloud Mac CI

DevOps и CI/CD ·~5 мин чтения

Как найти скрытые локальные зависимости чистой сборкой в Cloud Mac CI

Один и тот же проект Xcode может успешно собираться в давно используемом каталоге разработки, но после копирования в новое рабочее пространство VPSGit Cloud Mac сообщать об отсутствующих файлах и скриптах или пустых значениях конфигурации. В такой ситуации не стоит сначала переносить старые кеши. Важно проверить другое: может ли сборка завершиться, опираясь только на заданный коммит, явно объявленный набор инструментов и параметры, переданные конвейером.

Какие состояния нужно изолировать при чистом клонировании

Удаление DerivedData исключает лишь один вид кеша и не доказывает, что проект не зависит от локальной машины. В давно используемом рабочем пространстве обычно накапливаются незакоммиченные файлы, настройки в домашнем каталоге пользователя, глобально установленные инструменты, сценарии инициализации Shell и абсолютные пути за пределами репозитория.

Необходимо изолировать как минимум пять уровней:

Цель проверки чистого клонирования — не «очистить сборку ещё раз», а доказать, что никакое состояние, не зафиксированное в репозитории, списке зависимостей или параметрах конвейера, не используется неявно.

При первом расследовании следует сохранить контрольную пару: сборка в исходном рабочем пространстве проходит, а в изолированном — завершается ошибкой. В обоих случаях должны использоваться одинаковые коммит, Scheme, Configuration и Destination. Только тогда различие в результатах будет полезно для диагностики.

Создание одноразового изолированного рабочего пространства

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

#!/bin/bash
set -euo pipefail

SOURCE_REPO="${1:?source repository required}"
REVISION="${2:?revision required}"
PROJECT="${3:?project path required}"
SCHEME="${4:?scheme required}"

ROOT="$(mktemp -d "${TMPDIR:-/tmp}/clean-checkout.XXXXXX")"
trap 'rm -rf "$ROOT"' EXIT

git clone --no-local --no-checkout "$SOURCE_REPO" "$ROOT/repo"
git -C "$ROOT/repo" checkout --detach "$REVISION"
git -C "$ROOT/repo" submodule update --init --recursive

mkdir -p "$ROOT/home" "$ROOT/tmp" "$ROOT/DerivedData"
export HOME="$ROOT/home"
export TMPDIR="$ROOT/tmp"
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

cd "$ROOT/repo"
xcodebuild \
  -project "$PROJECT" \
  -scheme "$SCHEME" \
  -configuration Debug \
  -derivedDataPath "$ROOT/DerivedData" \
  CODE_SIGNING_ALLOWED=NO \
  build | tee "$ROOT/build.log"

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

Как выявлять отсутствующие зависимости как можно раньше

После завершения клонирования сначала выполните git status --porcelain: вывод должен быть пустым. Затем проверьте состояние подмодулей и полноту объектов больших файлов. Если проект включает генерацию кода, необходимо чётко разделять два вида артефактов: исходный код и версия генератора должны быть зафиксированы, а результаты генерации — либо добавлены в репозиторий в соответствии с правилами команды, либо стабильно создаваться во время сборки.

Не добавляйте ~/bin, /usr/local/bin или рабочий стол конкретного сотрудника в пути в качестве временного обходного решения. Вспомогательные инструменты должны находиться в каталоге инструментов репозитория, быть объявлены в управляемом пакетным менеджером списке с ограничением версий либо устанавливаться в фиксированной версии при инициализации узла.

Поиск скрытых входных данных по журналу ошибок

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

Признак ошибки Типичная причина Способ исправления
No such file or directory Незакоммиченный файл или абсолютный путь Добавить файл в репозиторий и использовать путь относительно репозитория
command not found Зависимость от интерактивного Shell или глобального инструмента Объявить версию инструмента и явно задать PATH
Пустое значение конфигурации Параметр существует только в локальной конфигурации Передавать через CI и немедленно завершать задачу при отсутствии
Модуль доступен только в старом каталоге Общий кеш скрывает отсутствующую зависимость Исправить декларацию зависимостей, а не копировать старый кеш
Ошибка прав доступа к сценарию Бит исполнения не попал в коммит Исправить режим файла и создать новый коммит

Следующие команды позволяют проверить, действительно ли текущий коммит содержит нужный файл и правильные права доступа:

git ls-tree -r HEAD -- path/to/file
git status --short
git diff --summary HEAD

Если путь в сообщении об ошибке указывает на /Users/某个名字/, обычно это означает, что в конфигурации проекта, сценарии или сгенерированном файле записан абсолютный путь. Следует исправить исходную настройку, создавшую этот путь, а не создавать на новом узле каталог с таким же именем.

Минимизация окружения без ложных ошибок

Прямой запуск через env -i помогает быстро обнаружить зависимости от окружения, но одновременно может удалить необходимые для сборки PATH, региональные настройки и временный каталог. Надёжнее сначала зафиксировать переменные, которые фактически читает текущая сборка, а затем сформировать белый список.

Рекомендуется сохранить HOME, TMPDIR, PATH, DEVELOPER_DIR, LANG и прикладные параметры, явно передаваемые конвейером. Для каждой пользовательской переменной следует добавить проверку обязательного значения, например:

: "${API_BASE_URL:?API_BASE_URL must be provided by CI}"
: "${BUILD_FLAVOR:?BUILD_FLAVOR must be provided by CI}"

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

Включение проверки в повседневный конвейер

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

Для успешного прохождения проверки должны одновременно выполняться следующие условия:

  1. Указанный коммит полностью извлекается в независимом клоне.
  2. До и после сборки в рабочем дереве нет непредвиденных изменений.
  3. Сборка не читает исходное рабочее пространство и личные каталоги пользователей.
  4. Для всех вспомогательных инструментов можно проследить источник версии.
  5. После очистки DerivedData по-прежнему создаются ожидаемые артефакты.
  6. Журнал ошибки и сводка окружения позволяют воспроизвести проблему, но не содержат конфиденциальных значений.

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

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

Почему недостаточно выполнить git clean перед сборкой?

git clean удаляет только неотслеживаемые файлы текущего рабочего дерева. Он не изолирует HOME, глобальные инструменты, общие кэши, Keychain и абсолютные пути за пределами репозитория.

Что проверять первым после сбоя чистой сборки?

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

Нужно ли запускать полную проверку для каждого коммита?

Нет. Короткую проверку можно оставить в обычном потоке, а полную изоляцию запускать перед выпуском, после обновления Xcode или зависимостей и при слиянии в основную ветку.

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

Настройте облачный Mac для следующей сборки или задачи машинного обучения

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

Настроить облачный Mac