Один и тот же пакет Swift корректно генерирует код на компьютере разработчика, но в чистом рабочем каталоге на облачном Mac завершается ошибкой Operation not permitted. Бывает и так, что сборка проходит успешно, но компилируются по-прежнему старые файлы. Обычно причина не в производительности машины, а в том, что плагин инструмента сборки использует необъявленные файлы, записывает данные в неверный каталог или маскирует проблему локальными артефактами от предыдущих сборок. Вместо многократной очистки кеша нужно последовательно восстановить все входы, выходы и границы выполнения плагина.
Сначала определите, на каком уровне происходит сбой
В работе плагина инструмента сборки SwiftPM участвуют три уровня: SwiftPM планирует команды, исполняемый файл плагина генерирует содержимое, а Xcode затем использует созданные файлы. Сначала сохраните полный журнал из корня репозитория:
set -o pipefail
rm -rf .ci
mkdir -p .ci
xcodebuild \
-scheme App \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$PWD/.ci/DerivedData" \
-clonedSourcePackagesDirPath "$PWD/.ci/SourcePackages" \
build 2>&1 | tee .ci/build.log
grep -E 'sandbox|deny|plugin|Operation not permitted|No such file' .ci/build.log
Если журнал показывает, что инструмент плагина вообще не найден, проверьте, объявлен ли он как исполняемый инструмент целевого объекта пакета. Если сбой происходит уже после запуска инструмента, продолжайте проверку путей чтения и записи. Если сборка завершается успешно, но сгенерированное содержимое не обновляется, прежде всего сверьте объявления входных и выходных файлов.
Не пытайтесь первым делом исправить проблему отключением песочницы. Отказ песочницы часто точно указывает, что генератор читает или изменяет данные за пределами графа сборки. Обход ограничения лишь откладывает проблему до следующей чистой сборки.
Включите входы и выходы в граф сборки
Надёжный плагин читает только явно объявленные входы и записывает результат в pluginWorkDirectory. В следующей структуре и schema, и сгенерированный файл Swift передаются под контроль SwiftPM:
import PackagePlugin
@main
struct CodegenPlugin: BuildToolPlugin {
func createBuildCommands(
context: PluginContext,
target: Target
) async throws -> [Command] {
let tool = try context.tool(named: "schema-gen")
let input = target.directory.appending("Schemas/api.json")
let output = context.pluginWorkDirectory
.appending("Generated/API.swift")
return [
.buildCommand(
displayName: "Generate API.swift",
executable: tool.path,
arguments: [input.string, output.string],
inputFiles: [input],
outputFiles: [output]
)
]
}
}
После запуска генератор должен самостоятельно создать родительский каталог Generated и записывать данные только по переданному выходному пути. Не следует по умолчанию писать в Sources репозитория, на рабочий стол, в домашний каталог пользователя или по фиксированному пути /Users/.... Плагин инструмента сборки предназначен для создания производных файлов, участвующих в текущей компиляции. Если задача должна изменять содержимое репозитория, её следует реализовать как командный плагин, явно запускаемый разработчиком, а не как скрытое изменение исходного кода при каждой сборке.
Выявляйте неявные зависимости
К типичным неявным входам относятся конфигурационные файлы из текущего рабочего каталога, шаблоны по путям из переменных окружения, кеши в домашнем каталоге и целые папки, которые генератор автоматически сканирует. Последовательно ответьте на следующие четыре вопроса:
| Проверка | Правильный подход | Признак риска |
|---|---|---|
| Входные файлы | Перечислить все в inputFiles |
Рекурсивное сканирование необъявленного каталога во время выполнения |
| Выходные файлы | Перечислить все в outputFiles |
Имена файлов зависят от времени или машины |
| Рабочий путь | Использовать абсолютный путь, переданный в аргументах | Зависимость от pwd или домашнего каталога |
| Порядок генерации | Сортировать данные для стабильного вывода | Зависимость от порядка перечисления файловой системой |
Обеспечьте детерминированность самого генератора
Даже при корректных разрешениях генератор может постоянно нарушать инкрементальную сборку. Чаще всего это происходит из-за записи текущего времени, временного каталога или имени хоста в заголовок файла. Если одинаковые входные данные каждый раз дают разные байты, SwiftPM не сможет определить, было ли изменение реальным.
Дважды выполните чистую генерацию на облачном Mac и сравните контрольные суммы:
rm -rf .ci/first .ci/second
mkdir -p .ci/first .ci/second
.build/debug/schema-gen \
Schemas/api.json .ci/first/API.swift
.build/debug/schema-gen \
Schemas/api.json .ci/second/API.swift
shasum -a 256 .ci/first/API.swift .ci/second/API.swift
cmp .ci/first/API.swift .ci/second/API.swift
Обе контрольные суммы должны совпадать. Генератор также должен сортировать поля, файлы и объявления, использовать единообразные переводы строк и заменять целевой файл только при изменении содержимого. Можно сначала записать временный файл, сравнить его с текущим, а затем выполнить атомарное перемещение. Так прерванная сборка не оставит частично записанный файл Swift.
Воспроизводите проблемы с кешем в чистом рабочем каталоге
Успешная локальная сборка не означает, что все зависимости объявлены. Локальный DerivedData, старые выходы плагина или ранее вручную сгенерированные исходные файлы могут временно скрывать недостающие объявления. В облачной среде нужно как минимум один раз выполнить проверку без сохранённого состояния, а затем запустить вторую, инкрементальную сборку.
Проверка в два прохода
Перед первым проходом удалите выделенный каталог сборки, затем выполните сборку и убедитесь, что все генерируемые файлы созданы именно плагином. Во втором проходе запустите сборку повторно, не меняя входные данные, и проверьте, что плагин без причины не перезаписывает выходы. После этого измените только одно поле schema и выполните третий проход, чтобы убедиться, что соответствующий сгенерированный файл действительно обновился.
Следующая проверка поможет быстро обнаружить файлы, созданные не в том месте:
find "$PWD" -type f -name 'API.swift' -print
git status --short
В нормальной ситуации производные файлы находятся в каталоге сборки, а вывод git status не содержит контролируемых системой версий исходных файлов, автоматически изменённых плагином. Если во втором проходе плагин снова выполняется, проверьте, действительно ли существует выходной файл, не обновляет ли инструмент его временную метку принудительно и не объявлен ли какой-либо входной каталог слишком широко.
Зафиксируйте границы проверки на облачном Mac
В удалённой среде сборки HireVM или на других чистых узлах macOS рекомендуется включить следующие проверки в конвейер, а не полагаться на ручное наблюдение:
- Зафиксируйте выбранную версию Xcode и записывайте в начале журнала результаты
xcodebuild -versionиswift --version. - Назначьте каждому рабочему каталогу отдельные каталоги DerivedData и извлечённых пакетов, чтобы параллельные задачи не записывали данные в одни и те же места.
- Начинайте первую сборку с пустого каталога, чтобы убедиться, что плагин не зависит от старых файлов в домашнем каталоге.
- Сохраняйте подробные журналы сборки до и после сбоя плагина, но не записывайте в них токены, материалы для подписи или полный набор переменных окружения.
- Сравнивайте контрольные суммы ключевых сгенерированных файлов, подтверждая воспроизводимость генерации для одного и того же коммита.
- После изменения одного входного файла повторно запускайте сборку и проверяйте, что инкрементально обновляются только ожидаемые выходы.
- Проверяйте в панели управления доступные конфигурации и выбирайте ресурсы с учётом количества параллельных сборок. Проверка корректности плагина не должна зависеть от случайного состояния конкретной машины.
Конечная цель — не добиться того, чтобы одна сборка «случайно прошла», а предоставить SwiftPM полное описание этапа генерации: при изменении входов выполнять точную пересборку, при неизменных входах не запускать лишние операции и получать одинаковые артефакты в новом рабочем каталоге облачного Mac. После этого песочница перестаёт быть помехой и становится автоматической проверкой того, что границы сборки заданы корректно и полностью.
Часто задаваемые вопросы
Может ли плагин сборки SwiftPM изменять каталог исходников?
Такую схему лучше не использовать. Генерируемые файлы следует записывать в рабочий каталог плагина и перечислять в outputFiles. Изменение репозитория нужно выносить в командный плагин с явным запуском.
Почему локально плагин работает, а на облачном Mac получает отказ?
Оставшиеся локальные файлы могут скрывать необъявленный выход. Генератор также может зависеть от домашнего или текущего каталога и абсолютных путей. Чистая копия репозитория и фиксированные пути сборки выявляют такие зависимости.
Можно ли надолго отключить песочницу ради стабильной сборки?
Нет. Следует объявить все входы и выходы, ограничить запись разрешёнными каталогами и добиться побайтово одинакового результата при одинаковых входных данных.
Запустите следующую задачу на удалённом Mac на выделенном узле
Выберите одну из двух доступных конфигураций, пять узлов и период оплаты: день, неделя, месяц или квартал. Перед заказом можно полностью проверить конфигурацию и сумму в USD.