Swift Package Manager в Xcode — это новейший способ управления сторонними зависимостями Swift-проектов. Он очень аккуратно интегрирован и позволяет легко и чисто управлять зависимостями. По крайней мере, иногда.
TL;DR
В этой статье я рассказываю о борьбе с добавлением SPM в старый проект, содержащий фреймворки, не совместимые с симуляторами Apple Silicon iOS (arm64). Если вы спешите найти решение, переходите сразу к параграфу о конфигурациях сборки SPM.
При работе над greenfield-проектами SPM в большинстве случаев действительно легко использовать. Однако в реальности мы довольно часто сталкиваемся с работой над какими-то старыми проектами со всеми их причудами.
[Отказ от ответственности]: Описанные проблемы и их решения были протестированы на Xcode 13.2.1 и 13.4.1. Поведение может измениться в будущих релизах Xcode. Здесь вы можете найти репозиторий с примером проекта.
Проблема
На днях я пытался добавить Swift Package Manager в один из проектов, над которым я работаю, поскольку моя команда склонна переходить на SPM с других менеджеров зависимостей.
К моему удивлению, меня встретила большая жирная красная ошибка, препятствующая компиляции проекта:
Could not find module <package name> for target 'x86_64-apple-ios-simulator';
found: arm64, arm64-apple-ios-simulator
Какую бы библиотеку я ни хотел добавить в проект, это всегда заканчивалось одинаково: ошибка сообщала, что модуль для текущей архитектуры отсутствует. В тот момент я оставил идею медленного перехода на SPM. Когда я снова наткнулся на эту проблему, я окончательно решил, что с этим нужно что-то делать.
Корень всех зол
После множества проб и ошибок я наконец пришел к решению. Но прежде чем открыть бутылку шампанского, давайте попробуем подумать, почему это вообще произошло.
Сама проблема исходит не от других менеджеров зависимостей, а скорее от настроек сборки проекта. Возможно, вы знакомы с обходным решением для запуска проектов с устаревшими фреймворками (например, «жирный» .framework, не созданный для симулятора iOS arm64) на кремниевых машинах Apple.
Есть два основных варианта решения этой проблемы:
-
Запустить Xcode с помощью Rosetta — кажется хорошей идеей и действительно работает, но за это приходится платить. Время чистой сборки для нашего проекта увеличилось со 112 до 165 секунд. Это почти на 50% дольше, и я подозреваю другие потери производительности во время повседневной работы.
-
Исключите архитектуру arm64 для iOS simulator sdk из конфигурации сборки для разработки. Это довольно простое и умное решение проблемы. Исключение архитектуры arm64 для симулятора заставляет xcode собирать наше приложение для архитектуры x86_64 на симуляторе iOS, который совместим с нашим устаревшим фреймворком (сообщение stackoverflow).
Несмотря на то, что мы явно исключаем архитектуру arm64 для симулятора iOS, мы получаем ошибку, показанную выше. Более того, в ней явно говорится, что SPM создал нашу зависимость только для архитектуры arm64. Мы можем сделать вывод, что SPM игнорирует настройки сборки нашего проекта.
К сожалению, нет никакого (по крайней мере, прямого) способа настроить конфигурацию сборки зависимостей SPM. Но это работает в других проектах с точно такими же ограничениями. Поэтому в моем случае казалось очевидным, что должен существовать способ повлиять на конфигурацию сборки зависимостей SPM.
Конфигурации сборки SPM
Если мы изучим документацию, то увидим, что существует debug
и release
BuildConfiguration. Мы также можем заглянуть в исходный код, но там не так много полезной информации. Так что же означает конфигурация debug и release и при каких обстоятельствах они применяются?
Я полагаю, что у меня есть довольно ценные и, возможно, даже шокирующие наблюдения:
Debug build configuration применяется к зависимостям Swift Package Manager, если наша текущая конфигурация сборки содержит «debug» или «development» в своем имени (без учета регистра). Поэтому если вы пытаетесь собрать приложение в стандартной конфигурации Release, переименуйте Debug во что-то другое или создайте новую конфигурацию (например, Stage), и оно будет работать™. Это означает, что существует некая логика разбора имени конфигурации сборки проекта, которая затем преобразуется в различные конфигурации сборки зависимостей SPM.
Я хочу подчеркнуть это еще раз: как бы глупо это ни звучало, переименование конфигурации сборки проекта — это ключ к успеху. Она не может содержать Debug
или Development
в своем имени, чтобы собрать зависимости SPM в режиме Release
.
Обратная проблема
Мы только что пришли к выводу, что нам нужна релизная конфигурация зависимостей SPM для нашего проекта в сценарии, описанном выше. Но в прошлом я уже сталкивался с подобной проблемой, правда, с другой стороны. Поскольку мы пишем Unit Tests, мы также писали их для внутренних пакетов, которые мы использовали для разделения кода. Оказалось, что мы столкнулись с проблемами при попытке запустить наши юнит-тесты вместе с другими тестовыми целями в основной схеме проекта:
Module <package name> was not compiled for testing
Как вы уже догадались, решение здесь полностью противоположно тому, что мы только что сделали. Для того чтобы эта ошибка исчезла, мы должны скомпилировать наш Swift-пакет с отладочной конфигурацией, а это значит, что нам нужно добавить «debug» к имени нашей конфигурации разработки. Если в это же время вы решаете предыдущую проблему, то это невозможно. Мы решили ее, создав отдельную схему только для запуска модульных тестов локальных пакетов.
Выводы
Swift Package Manager — отличный способ управления сторонними зависимостями в проектах iOS. С новыми выпусками Xcode он становится все более полезным и мощным. К сожалению, SPM все еще является молодым дополнением к Xcode и ему не хватает некоторых функциональных возможностей. Кроме того, его эфемерный способ реализации приводит к проблемам, подобным тем, с которыми я столкнулся. В данном конкретном случае нам помогла бы явная документация, рассказывающая о логике разбора имен конфигурации сборки, применяемой к нашим Swift-пакетам. Если вы когда-нибудь окажетесь в подобной ситуации, надеюсь, эта статья поможет вам разобраться с ней 🙂