После нескольких недель работы крупного репозитория iOS на облачном Mac один из бранчей может внезапно начать выдавать в скрипте ошибку argument list too long, хотя соседний бранч по-прежнему успешно архивируется. Не спешите очищать DerivedData или списывать проблему на случайный сбой Xcode. Обычно эта ошибка возникает, когда execve возвращает E2BIG: аргументы команды, завершающие символы каждого аргумента и унаследованное окружение вместе исчерпывают пространство, доступное процессу.
Определите, на каком уровне происходит сбой
Сначала сохраните полную команду и статус завершения. Если в журнале указано только, что завершился с ошибкой этап скрипта, временно включите set -x в соответствующем Run Script и выясните, что именно не запускается: find, rm, средство архивации, генератор кода или драйвер компилятора. Не выводите всё окружение в общедоступный журнал: оно может содержать токены. Записывайте только имена переменных, количество байтов и структуру команды с удалёнными конфиденциальными данными.
Есть три распространённых сценария: подстановка шаблона одновременно разворачивается в десятки тысяч путей; скрипт объединяет все исходные файлы в одну переменную; CI передаёт объёмный JSON, содержимое сертификатов или многострочные настройки через переменные окружения. Длинные пути к рабочему каталогу и повторяющиеся аргументы -I, -F и -D также постоянно сокращают доступный запас.
Если команда работает в небольшом бранче, это ещё не означает, что она построена правильно. Сбой, возникающий только после увеличения количества файлов, обычно говорит о том, что способ передачи входных данных не рассчитан на рост.
Измерьте запас для аргументов и окружения
Доступный лимит в macOS нельзя оценить только по длине текста команды. Сначала выполните измерения с тем же пользователем и тем же способом запуска, которые использует задача сборки:
getconf ARG_MAX
python3 - <<'PY'
import os
limit = os.sysconf("SC_ARG_MAX")
env_bytes = sum(len(k) + len(v) + 2 for k, v in os.environ.items())
largest = sorted(
((len(k) + len(v) + 2, k) for k, v in os.environ.items()),
reverse=True
)[:10]
print("arg_max", limit)
print("environment_bytes", env_bytes)
for size, key in largest:
print(size, key)
PY
Полученный результат нужен для сравнения, а не как бюджет, который можно использовать полностью. Системе также требуется место для указателей, завершающих символов и накладных расходов при запуске. На практике следует оставлять заметный запас и отслеживать, не вырос ли размер окружения относительно исторического базового уровня.
Найдите разросшиеся переменные
В первую очередь проверьте PATH, пути поиска, временные каталоги, аргументы менеджеров зависимостей и переменные, добавляемые CI. Если конфигурация JSON занимает десятки KB, запишите её во временный файл с ограниченными правами доступа, а дочернему процессу передайте только путь к нему. Если PATH дополняется несколько раз, сначала удалите дубликаты вместо повторной конкатенации на каждом этапе.
Сократите унаследованное окружение, не нарушая работу инструментов
Не запускайте полную сборку Xcode напрямую в пустом окружении. Отсутствие HOME, PATH, временного каталога или каталога разработчика создаст новые сбои. Надёжнее сформировать список разрешённых переменных для отдельного инструмента:
env -i \
HOME="$HOME" \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
TMPDIR="$TMPDIR" \
DEVELOPER_DIR="$DEVELOPER_DIR" \
/bin/zsh -lc 'xcrun --find xcodebuild'
В рабочей задаче также необходимо добавить локаль, каталоги кеша и настройки прокси в соответствии с фактическими зависимостями. Конфиденциальные данные передавайте через файлы с коротким сроком жизни и удаляйте после использования. Обычные логические флаги и короткие идентификаторы можно оставить в переменных окружения. В задачах автоматизации на SetMini инициализацию окружения также следует сосредоточить в одном входном скрипте, чтобы интерактивная оболочка, CI runner и скрипты Xcode не добавляли одни и те же настройки по отдельности.
Уберите длинные списки файлов из командной строки
Используйте разделение нулевым символом в универсальных скриптах
Имена файлов могут содержать пробелы и переводы строк, поэтому нельзя использовать for f in $(find ...). Для инструментов с поддержкой пакетной обработки можно сочетать find -print0 и xargs -0:
find "$PWD/Artifacts" -type f -name '*.dSYM' -print0 |
xargs -0 -n 50 /usr/bin/file
Параметр -n 50 задаёт явный предел для каждого пакета. Если инструмент умеет читать список из стандартного ввода, отдавайте предпочтение этому способу, чтобы сократить количество повторных запусков процессов.
Используйте списки файлов на этапах Xcode
Входные и выходные данные Run Script следует записывать в .xcfilelist, а затем указывать в Input File Lists и Output File Lists. Благодаря этому Xcode сможет отслеживать зависимости, а скрипту не придётся разворачивать тысячи путей в одну команду. Если компилятор или компоновщик явно поддерживает response-файлы, стабильные аргументы можно записать в такой файл. Не предполагайте, что все сторонние инструменты понимают синтаксис @file.
Если через настройки сборки передаётся большое количество параметров, общие значения следует перенести в .xcconfig. Скрипт должен получать только несколько необходимых аргументов, наборы путей — через списки файлов, а структурированные настройки — через временные файлы. Разделение этих трёх видов входных данных также упрощает аудит журналов.
Добавьте контроль роста и регрессионные проверки
После исправления выполните как минимум одну чистую, а затем одну инкрементальную сборку бранча с наибольшим количеством файлов и самыми длинными путями. Проверьте, что скрипт корректно обрабатывает имена файлов с пробелами, ошибка пакетной обработки приводит к ненулевому статусу завершения, а временные файлы удаляются при любом выходе.
В начале задачи CI можно записывать ARG_MAX, общий размер окружения в байтах и имена самых крупных переменных, но не их значения. Установите командный базовый уровень размера окружения и выдавайте предупреждение при превышении порога, не дожидаясь, пока система откажется создавать процесс. Для растущих наборов файлов одновременно фиксируйте их количество и длину самого длинного пути.
Конечная цель состоит не в том, чтобы текущая команда лишь немного не достигала лимита, а в том, чтобы её длина перестала расти линейно вместе с репозиторием. Сохраняйте окружение компактным, передавайте наборы файлов по специализированным каналам и задавайте фиксированный размер пакетов. Тогда E2BIG превратится из случайного сбоя в проблему конфигурации, которую можно обнаружить заранее.
Часто задаваемые вопросы
Почему одна и та же команда завершается ошибкой только в некоторых ветках?
Аргументы и окружение используют общий лимит процесса. Ветка с большим числом файлов, длинными путями или дополнительными переменными может исчерпать оставшийся запас.
Когда использовать xargs, а когда response-файл?
Для обычной пакетной обработки используйте xargs с разделителем NUL. Если компилятор или линкер официально поддерживает response-файлы, передавайте длинный список через них.
Стоит ли увеличивать ARG_MAX в macOS?
Нет, это ненадёжная основа для CI. Сократите окружение и повторяющиеся параметры, а длинные наборы файлов передавайте через файл, стандартный ввод или ограниченные пакеты.
Перенесите этапы разработки на облачный Mac с доступом в любое время
Выберите одну из двух конфигураций Apple Silicon и четырёх дата-центров. Ресурсы не распределяются между арендаторами, а фактическая доступность отображается в консоли в реальном времени.