Creating Swift Package with binaryTarget that has dependencies

How can you distribute an XCFramework via Swift Package Manager when it has dependencies on other Swift packages? We accomplished this with CocoaPods in order to distribute our closed source SDK that has dependencies, but need to migrate to SPM. Note none of the types from the dependencies are used as part of our module’s public interface - usage is purely internal.

I’ve made a lot of progress following these steps for a simple example:

Create Framework Project

  1. Create a new iOS Framework project in Xcode and name it WallpaperKit
  2. In the project settings select the target and verify in Build Settings that Build Libraries for Distribution is Yes then set Skip Install to No
  3. Create a new UIViewController subclass, name it WallpaperPreviewViewController, make it public, and add some functionality to it to show a UIImageView
  4. Add a new Package Dependency in the project settings, for this example we’ll use https://github.com/onevcat/Kingfisher, and specify exact version 8.5.0
  5. Add internal import Kingfisher and use it in WallpaperPreviewViewController to download and show an image from the web
  6. Close the WallpaperKit project

Create Hosting App Project (this makes it easier to develop the framework functionality and test it in an app with Xcode building both in the same workspace)

  1. Create a new iOS app project and name it WallpaperApp
  2. Create a new workspace named WallpaperApp
  3. Close the WallpaperApp project
  4. Drag and drop WallpaperApp.xcodeproj into the workspace’s sidebar
  5. Drag and drop WallpaperKit.xcodeproj into the workspace’s sidebar
  6. Switch the scheme to WallpaperKit and build
  7. Select WallpaperApp project, then with WallpaperApp target selected, in the General tab under Frameworks, Libraries, and Embedded Content, click + and add WallpaperKit.framework
  8. In ViewController.swift, import WallpaperKit and add functionality to present an instance of WallpaperPreviewViewController
  9. Run the app and verify it works

Create XCFramework

  1. In Terminal, cd into WallpaperKit and run xcodebuild archive -scheme WallpaperKit -configuration Release -destination 'generic/platform=iOS' -archivePath './build/WallpaperKit.framework-iphoneos.xcarchive' SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES DEFINES_MODULE=YES
  2. Run xcodebuild archive -scheme WallpaperKit -configuration Release -destination 'generic/platform=iOS Simulator' -archivePath './build/WallpaperKit.framework-iphonesimulator.xcarchive' SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES DEFINES_MODULE=YES
  3. Run xcodebuild -create-xcframework -framework './build/WallpaperKit.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/WallpaperKit.framework' -framework './build/WallpaperKit.framework-iphoneos.xcarchive/Products/Library/Frameworks/WallpaperKit.framework' -output './build/WallpaperKit.xcframework'
  4. Open the build folder and retrieve the XCFramework

Create Swift Package

  1. Create a new package in Xcode, select Library, and name it WallpaperKitDist
  2. Drag and drop WallpaperKit.xcframework into Sources
  3. Create a new directory in Sources called WallpaperKitDependencies
  4. Create a new Swift file in WallpaperKitDependencies called WallpaperKitDependencies (SPM requires a Swift file to recognize WallpaperKitDependencies as a valid target and fetch dependencies)
  5. Open Package.swift and change it to
import PackageDescription

let package = Package(
    name: "WallpaperKit",
    platforms: [
        .iOS(.v18)
    ],
    products: [
        .library(
            name: "WallpaperKit",
            targets: ["WallpaperKit", "WallpaperKitDependencies"]
        ),
    ],
    dependencies: [
        .package(
            url: "https://github.com/onevcat/Kingfisher.git",
            exact: "8.5.0"
        )
    ],
    targets: [
        .binaryTarget(
            name: "WallpaperKit",
            path: "./Sources/WallpaperKit.xcframework"
        ),
        .target(
            name: "WallpaperKitDependencies",
            dependencies: [
                "Kingfisher"
            ],
            path: "./Sources/WallpaperKitDependencies"
        )
    ]
)

Create Test App (to simulate a third-party app using the package)

  1. Create a new iOS app project and name it TestApp
  2. Add a new Local package selecting the WallpaperKitDist directory that contains Package.swift
  3. Import WallpaperKit and use it to present a WallpaperPreviewViewController

This works! Though the console logs objc[39953]: Class _TtC10KingfisherP33_6AA794C9C370CDB07604B4D8B99AEAA312BundleFinder is implemented in both /Users/Name/Library/Developer/Xcode/DerivedData/TestApp-capvhjiqxrdgdnbevpkajicnjpcs/Build/Products/Debug-iphonesimulator/WallpaperKit.framework/WallpaperKit (0x100e8bbf8) and /Users/Name/Library/Developer/CoreSimulator/Devices/E0AF13C2-874C-47B9-B864-72AF3E4D5D4B/data/Containers/Bundle/Application/AF32011A-92E7-4E26-9A97-9F0C25C07863/TestApp.app/TestApp.debug.dylib (0x101a543b0). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.

I thought using internal import Kingfisher (or @_implementationOnly import Kingfisher) would have resolved this, but seems to make no difference compared to just import Kingfisher. I suspect it might not be an issue as long as the Kingfisher version number specified in the distribution Package.swift matches the version used in the framework project (and the app does not add a different version as a dependency), but not positive.

Can these warnings be resolved, or is it not a concern in this setup? Is this the best solution to distribute an XCFramework via Swift Package Manager that has dependencies on other Swift packages for now or is there a better approach? Thanks!

Creating Swift Package with binaryTarget that has dependencies
 
 
Q