Один и тот же репозиторий без проблем работает на машине разработчика, но в удаленной задаче сборки возникает ошибка Module not found или из готового пакета пропадает ресурс. Не спешите снова очищать кеши. Файловая система, которая обычно используется в macOS по умолчанию, не различает регистр в именах файлов, поэтому Config.json и config.json могут восприниматься как один и тот же путь. Скрытая проблема проявится только тогда, когда скрипт, зависимость или целевая среда развертывания потребует точного совпадения регистра. Самый надежный способ диагностики — не менять системный диск, а подключить на облачном Mac изолированный том APFS с учетом регистра.
Как определить, связан ли сбой с регистром пути
Сначала найдите путь в журнале неудачной сборки, а не ограничивайтесь кодом завершения в последней строке. В первую очередь стоит проверить следующие ситуации:
| Симптом | Возможная причина | Как проверить |
|---|---|---|
| Локально импорт работает, но задача сборки не находит модуль | Регистр в import не совпадает с реальным именем файла | Сверить полный путь с помощью git ls-files |
| После коммита из двух ресурсов остается только один | Имена различаются только регистром | Повторно клонировать репозиторий на том с учетом регистра |
| Первая сборка после очистки завершается ошибкой | Старые артефакты скрывали неверную ссылку | Удалить каталог вывода и выполнить полную сборку |
| На этапе упаковки один файл перезаписывает другой | Скрипт приводит имена к единому регистру | Проверить операции копирования и переименования |
Также выполните diskutil info / и запишите значение File System Personality для текущего системного тома. Эта команда нужна только для проверки среды. Не пытайтесь напрямую преобразовать системный том, с которого запущена macOS.
Проблемы с путями необходимо воспроизводить в чистем рабочем каталоге. Если скопировать старый кеш на тестовый том, он снова может скрыть исходную ошибку.
Создание изолированного тестового тома APFS
Разреженный образ увеличивается по мере записи данных, поэтому хорошо подходит для временной диагностики. Следующие команды создают тестовый том с максимальным размером 80 ГБ. Если проект использует крупные зависимости, рассчитайте объем с учетом пикового размера репозитория, зависимостей и артефактов сборки.
mkdir -p "$HOME/apfs-lab"
hdiutil create \
-size 80g \
-type SPARSEBUNDLE \
-fs "Case-sensitive APFS" \
-volname BuildCase \
"$HOME/apfs-lab/BuildCase.sparsebundle"
hdiutil attach "$HOME/apfs-lab/BuildCase.sparsebundle"
diskutil info "/Volumes/BuildCase"
Убедитесь, что в выводе явно указана чувствительность к регистру, а точкой монтирования является /Volumes/BuildCase. Не храните на этом временном томе ключи доступа, материалы для подписи или данные, предназначенные для длительного хранения.
Повторное получение репозитория на новом томе
Не перетаскивайте файлы напрямую из прежнего рабочего каталога: конфликтующие файлы там уже могли быть потеряны. Повторно клонируйте проект из контролируемого репозитория и создайте отдельный каталог сборки:
mkdir -p /Volumes/BuildCase/work
cd /Volumes/BuildCase/work
git clone "$REPOSITORY_URL" project
cd project
git status --short
На этом этапе вывод git status должен быть пустым. Если уже во время клонирования появляется ошибка о существующем целевом пути, обычно это означает, что в дереве репозитория есть пути, совпадающие после приведения регистра.
Аудит имен файлов Git и ссылок в скриптах
Следующий скрипт только считывает пути, отслеживаемые Git, нормализует их в Unicode и группирует без учета регистра. Файлы при этом не изменяются:
from collections import defaultdict
import subprocess
import unicodedata
raw = subprocess.check_output(
["git", "ls-files", "-z"],
text=True
)
groups = defaultdict(list)
for path in raw.split(" "):
if not path:
continue
key = unicodedata.normalize("NFC", path).casefold()
groups[key].append(path)
found = False
for paths in groups.values():
if len(paths) > 1:
found = True
print("COLLISION")
for path in paths:
print(f" {path}")
raise SystemExit(1 if found else 0)
Сохраните его как tools/check_path_case.py и выполните python3 tools/check_path_case.py. Если команда завершается с кодом 1, приведите имена к единому варианту и зафиксируйте изменения через git mv. Когда меняется только регистр букв, сначала можно присвоить файлу промежуточное имя, а затем нужное. Это не позволит прежнему рабочему каталогу проигнорировать изменение:
git mv Sources/config.json Sources/config.tmp
git mv Sources/config.tmp Sources/Config.json
Отсутствие конфликтов в именах еще не означает, что ссылки указаны правильно. Продолжите поиск устаревших путей в скриптах сборки, списках ресурсов, конфигурации проекта и тестовых фикстурах. Особое внимание уделите расхождениям между автоматически созданными файлами и конфигурацией, написанной вручную.
Включение проверки в воспроизводимую сборку
Тестовая задача должна сначала проверить точку монтирования, затем выполнить аудит путей и только после этого запускать сборку из пустого каталога. Так при отсутствии тестового тома файлы не будут случайно записаны обратно на системный диск.
set -euo pipefail
test -d /Volumes/BuildCase
cd /Volumes/BuildCase/work/project
python3 tools/check_path_case.py
rm -rf .build-output
mkdir .build-output
./scripts/build.sh "$PWD/.build-output"
Если в проекте нет единой точки входа для сборки, сначала оберните существующие команды в scripts/build.sh и явно передавайте каталог вывода. Кеш зависимостей можно подключить отдельно, но при первой проверке старый кеш следует отключить. После успешной сборки возвращайте его компоненты по одному, чтобы определить, на каком уровне появляется ошибочный путь.
Типичные ошибки и завершающая проверка
Перед завершением диагностики проверьте каждый пункт:
- Репозиторий повторно клонирован на тестовый том, а не скопирован из прежнего каталога.
- Имена файлов исправлены через
git mv, и в коммите явно отображается переименование. - Скрипты не приводят пути принудительно к нижнему или верхнему регистру.
- При совпадении целевых имен команда копирования ресурсов завершается ошибкой, а не молча перезаписывает файлы.
- Артефакты сборки, каталоги зависимостей и журналы находятся в ожидаемой точке монтирования.
- Чистая и инкрементальная сборки выполнены по одному разу и дают одинаковый результат.
После проверки сначала экспортируйте необходимые журналы, а затем отключите тестовый том:
hdiutil detach "/Volumes/BuildCase"
Удаляйте BuildCase.sparsebundle только после того, как убедитесь, что данные больше не нужны. Если проверка должна использоваться как постоянный контроль качества, сохраняйте скрипт создания тома, а не тестовые данные. В начале каждой задачи проверяйте формат тома, доступное пространство и путь монтирования.
Часто задаваемые вопросы
Нужно ли переводить системный том облачного Mac в режим учета регистра?
Нет. Создайте отдельный разреженный образ APFS и разместите на нем только репозиторий и каталоги сборки. Системный том останется без изменений.
Почему Git не всегда сообщает о конфликте регистра в именах файлов?
Git хранит написание путей, но рабочий каталог без учета регистра может сопоставить два имени одному файлу. Репозиторий следует заново клонировать на тестовый том.
Нужен выделенный физический узел Mac mini
Просмотрите доступные конфигурации и узлы, а также фиксированные расчётные периоды, чтобы перенести шаги статьи в облачную среду Mac для постоянного использования.