Создание XCFramework из SPM пакета

Привет, Хабр! Я Николай Чурянин, начальник отдела разработки приложений для юридических лиц. Как-то нам в голову пришла мысль подключать зависимости в виде собранных библиотек и тем самым не тратить время на пересборку редко изменяемых зависимостей. В данной статье рассмотрим, как создавать XCFramework из SPM пакета.

Обратимся к документации, как Apple рекомендует создавать бинарный артефакт.

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

  • SKIP_INSTALL=NO,  чтобы продукт был включен в архив,

  • BUILD_LIBRARY_FOR_DISTRIBUTION=YES,  чтобы был сгенерирован интерфейс продукта.

И выполнить команду для создания архива:

xcodebuild archive
    -project MyFramework.xcodeproj
    -scheme MyFramework
    -destination "generic/platform=iOS"
    -archivePath "archives/MyFramework"

Сразу за ней — команду сборки xcframework«а:

xcodebuild -create-xcframework
    -archive archives/MyFramework-iOS.xcarchive -framework MyFramework.framework
    -archive archives/MyFramework-iOS_Simulator.xcarchive -framework MyFramework.framework
    -archive archives/MyFramework-macOS.xcarchive -framework MyFramework.framework
    -archive archives/MyFramework-Mac_Catalyst.xcarchive -framework MyFramework.framework
    -output xcframeworks/MyFramework.xcframework

Примеры из документации.

Попробуем то же самое сделать для SPM пакета. Создадим новый пакет MyFramework. Указываем тип библиотеки .dynamic,  т.к. для создания необходим .framework.

 Package.swift

let package = Package(
    name: "MyFramework",
    products: [
        .library(
            name: "MyFramework",
            type: .dynamic,
            targets: ["MyFramework"]),
    ],
    dependencies: [
    ],
    targets: [
        .target(
            name: "MyFramework",
            dependencies: []),
        .testTarget(
            name: "MyFrameworkTests",
            dependencies: ["MyFramework"]),
    ]
)

Вызовем команду создания xcarchive:

xcodebuild archive
    -scheme MyFramework
    -destination "generic/platform=iOS"
    -archivePath "archives/MyFramework.xcarchive"
    SKIP_INSTALL=NO
    BUILD_LIBRARY_FOR_DISTRIBUTION=YES

И следом команду создания xcframework:

xcodebuild -create-xcframework
    -archive "archives/MyFramework.xcarchive"
    -framework MyFramework.framework
    -output xcframeworks/MyFramework.xcframework

В результате выполнения последней команды получаем следующую ошибку:

error: the path does not point to a valid framework: /archives/MyFramework.xcarchive/Products/Library/Frameworks/MyFramework.framework

Данная ошибка означает, что по искомому пути нет MyFramework.framework. Попробуем разобраться. Для этого посмотрим,  что находится внутри созданного MyFramework.xcarchive:

a089ec3d9e8c24b41e26cb04b32ee669.png

А внутри MyFramework.framework по искомому пути действительно нет, ошибка вполне логичная.

В официальной документации создание бинарного артефакта основывается на проекте .xcodeproj,  где уже выставлены все значения переменных окружения, от которых зависит сборка. Проанализируем все переменные окружения и выставим необходимые значения. Их описание можно найти по ссылкам:  раз,  два.

Во-первых, нам необходимо изменить путь установки .framework. Для этого необходимо указать каталог, в который помещается продукт внутри архива, в переменную окружения INSTALL_PATH:

INSTALL_PATH=/Library/Frameworks

d68b9bcce1199a9f46dc5aecf271abef.png

В результате .framework находится там, где и ожидает команда сборки xcframework,  и, как следствие, бинарный артефакт успешно создаётся:

xcframework successfully written out to: /xcframework/MyFramework.xcframework

Но вот при использовании получаем следующую ошибку:

1a83baf2d01bddd241b84d4ba12daa50.png

Простыми словами, данная ошибка означает, что потребители данного xcframework не могут понять интерфейс.

Для исправления ситуации установим тип продукта и каталог, в который будет помещен интерфейс, в следующие переменные окружения соответственно:

PRODUCT_TYPE=com.apple.product-type.framework MODULES_FOLDER_PATH=MyFramework.framework/Modules

0ac1091169b98ceadca3801b6f4cbde4.png

В результате архив содержит .swiftmodule. При использовании xcframework,  созданного на основе такого архива, ошибок уже нет.

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

SWIFT_INSTALL_OBJC_HEADER=YESPUBLIC_HEADERS_FOLDER_PATH=MyFramework.framework/Headers    

0f6f979ef56c04262e0b252deb95b45e.png

Команда для создания архива в результате выглядит следующим образом:

xcodebuild archive
    -scheme MyFramework
    -destination "generic/platform=iOS"
    -archivePath "archives/MyFramework.xcarchive"
    SKIP_INSTALL=NO
    BUILD_LIBRARY_FOR_DISTRIBUTION=YES
    INSTALL_PATH=/Library/Frameworks
    MODULES_FOLDER_PATH=MyFramework.framework/Modules
    PRODUCT_TYPE=com.apple.product-type.framework
    PUBLIC_HEADERS_FOLDER_PATH=MyFramework.framework/Headers
    SWIFT_INSTALL_OBJC_HEADER=YES

Итак, создание XCFramework из SPM пакета по своей сути ничем не отличается от создания из xcodeproj. Тем не менее нужно погрузиться глубже, чтобы понять, как это сделать. Например,  тут реализован внушительных размеров скрипт для выполнения данной задачи.

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

© Habrahabr.ru