skip to Main Content

I’m trying to integrate a binaryTarget into a project using Swift Package Manager. The binary comes as a .zip containing the main ffmpegkit.xcframework plus 7 additional .xcframework‘s that it depends on.

Here’s the folder structure from DerivedData/.../SourcePackages/artifacts/... after SPM has unzipped it.
Unzipped folder showing the 8 frameworks

This is my Package.swift file:

// swift-tools-version: 5.10

import PackageDescription

let package = Package(
    name: "converter",
    platforms: [
        .macOS(.v13),
        .iOS(.v14),
    ],
    products: [
        .library(
            name: "converter",
            targets: ["converter"])
    ],
    targets: [
        .target(
            name: "converter",
            dependencies: [
                .target(name: "ffmpeg-iOS", condition: .when(platforms: [.iOS])),
                .target(name: "ffmpeg-macOS", condition: .when(platforms: [.macOS]))
            ],
            path: "Sources/converter"
        ),
        .binaryTarget(name: "ffmpeg-iOS",
                      url: "https://github.com/arthenica/ffmpeg-kit/releases/download/v6.0/ffmpeg-kit-full-6.0-ios-xcframework.zip",
                      checksum: "c87ea1c77f0a8a6ba396c22fc33e9321befb8e85f8e8103046ddeb74fea66182"),
        .binaryTarget(name: "ffmpeg-macOS",
                      url: "https://github.com/arthenica/ffmpeg-kit/releases/download/v6.0/ffmpeg-kit-full-6.0-macos-xcframework.zip",
                      checksum: "8cab26eecd43b9389d37f64efaf43b9c6baf4e53614b62e6209d8ee8681b94b9")
    ]
)

Now when I build and run the project, it crashes with dyld[85157]: Library not loaded: @rpath errors as the build only seems to include the ffmpegkit.xcframework folder and not the others.

This happens even if I do not import ffmpegkit in any of my code. So what is making the build choose to add the ffmpegkit framework but not the others?

How can I tell SPM to include these additional frameworks when it builds?

—- UPDATE —-

I have since found a workaround, although not ideal. I have created a new GitHub repo that contains the 7 zipped .xcframework files. I have added these as their own .binaryTarget‘s in the Package.swift. They now are downloaded and installed to the app whenever the package is used. However the original ffmpegkit.xcframework still contains the 7 frameworks, although not used.

So i’d still like to know 2 things:

  1. What makes SPM choose to embed the ffmpegkit framework and none of the other 7? Is it just the first alphabetically?

  2. How can I make SPM just use all the frameworks that are already packaged together?

2

Answers


  1. To answer your questions:

    Q1. I think what makes SPM choose to embed the ffmpegkit.xcframework framework but not the others is most likely because SPM treats each binaryTarget as an isolated framework and doesn’t attempt to look inside the .xcframework to resolve any additional dependencies (such as the libav* frameworks). It only includes the main framework specified in your Package.swift. In this case, ffmpegkit.xcframework is the only one explicitly listed, and thus the only one SPM included.

    In short! I will say SPM does not go beyond what is directly specified.

    Q2. To make SPM just use all the frameworks that are already packaged together. You need to explicitly declare all the dependent frameworks within your Package.swift file, even if they are contained inside the main ffmpegkit.xcframework. SPM doesn’t resolve these internal dependencies on its own, so you must include all the necessary .xcframework files as separate binaryTargets.

    Let me give an instance of how you can modify the Package.swift to include all of the dependent frameworks

    // swift-tools-version: 5.10
    
    import PackageDescription
    
    let package = Package(
        name: "converter",
        platforms: [
            .macOS(.v13),
            .iOS(.v14)
        ],
        products: [
            .library(
                name: "converter",
                targets: ["converter"]
            )
        ],
        targets: [
            .target(
                name: "converter",
                dependencies: [
                    .target(name: "ffmpeg-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "ffmpeg-macOS", condition: .when(platforms: [.macOS])),
                    .target(name: "libavcodec-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "libavdevice-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "libavfilter-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "libavformat-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "libavutil-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "libswresample-iOS", condition: .when(platforms: [.iOS])),
                    .target(name: "libswscale-iOS", condition: .when(platforms: [.iOS]))
                ],
                path: "Sources/converter"
            ),
            .binaryTarget(
                name: "ffmpeg-iOS",
                url: "https://github.com/arthenica/ffmpeg-kit/releases/download/v6.0/ffmpeg-kit-full-6.0-ios-xcframework.zip",
                checksum: "c87ea1c77f0a8a6ba396c22fc33e9321befb8e85f8e8103046ddeb74fea66182"
            ),
            .binaryTarget(
                name: "ffmpeg-macOS",
                url: "https://github.com/arthenica/ffmpeg-kit/releases/download/v6.0/ffmpeg-kit-full-6.0-macos-xcframework.zip",
                checksum: "8cab26eecd43b9389d37f64efaf43b9c6baf4e53614b62e6209d8ee8681b94b9"
            ),
            .binaryTarget(
                name: "libavcodec-iOS",
                url: "<url-to-your-zipped-libavcodec.xcframework>",
                checksum: "<checksum-of-libavcodec>"
            ),
            .binaryTarget(
                name: "libavdevice-iOS",
                url: "<url-to-your-zipped-libavdevice.xcframework>",
                checksum: "<checksum-of-libavdevice>"
            ),
            .binaryTarget(
                name: "libavfilter-iOS",
                url: "<url-to-your-zipped-libavfilter.xcframework>",
                checksum: "<checksum-of-libavfilter>"
            ),
            .binaryTarget(
                name: "libavformat-iOS",
                url: "<url-to-your-zipped-libavformat.xcframework>",
                checksum: "<checksum-of-libavformat>"
            ),
            .binaryTarget(
                name: "libavutil-iOS",
                url: "<url-to-your-zipped-libavutil.xcframework>",
                checksum: "<checksum-of-libavutil>"
            ),
            .binaryTarget(
                name: "libswresample-iOS",
                url: "<url-to-your-zipped-libswresample.xcframework>",
                checksum: "<checksum-of-libswresample>"
            ),
            .binaryTarget(
                name: "libswscale-iOS",
                url: "<url-to-your-zipped-libswscale.xcframework>",
                checksum: "<checksum-of-libswscale>"
            )
        ]
    )
    

    Added each .xcframework as a binaryTarget: I included each of the 7 additional frameworks (libavcodec, libavdevice, etc.) as a binaryTarget with its own URL and checksum.

    Login or Signup to reply.
  2. Question 1: How to Make SPM Use Local Frameworks Without Redownloading?

    When multiple frameworks are present in the same folder inside a .zip file, SPM doesn’t explicitly "choose" between them—it expects that each binary target corresponds to a single platform or architecture. If multiple frameworks are in the same folder and there’s no clear separation of platform or architecture, this can cause ambiguity.

    Here’s how SPM typically works:

    • SPM looks at the current platform and architecture of the target (iOS on arm64, macOS on x86_64…). Then tries to locate the framework that corresponds to that platform/architecture.

    If multiple frameworks (ffmpegkit.framework, libavcodec.framework, …) are in the same folder, SPM might fail to differentiate which framework to use, since there’s no structure guiding it toward the correct one. SPM may just pick the first framework it finds, which is often based on how the .zip file is structured.

    Summary:

    • Platform and architecture are key: SPM chooses based on platform (iOS vs. macOS) and architecture (arm64 vs. x86_64).

    • Framework ambiguity: If multiple frameworks target the same platform/architecture in the same folder without clear naming or organization, SPM might choose the wrong one.

    Apple Documentation:

    https://developer.apple.com/documentation/packagedescription
    
    https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages
    

    SPM downloads the framework from the URL specified in the Package.swift, to use pre-downloaded frameworks without forcing SPM to download them again, you need to use a local package.

    Step-by-step:

    1 – Organize your .xcframeworks in a directory structure:

    FFmpegKit/
    ├── ffmpeg-iOS.xcframework
    ├── ffmpeg-macOS.xcframework
    └── Package.swift
    

    2 – Create the Package.swift file with the content above inside the FFmpegKit folder.

    3 – In Xcode:

    • Go to File > Add Packages….
    • Click "Add Local…".
    • Select the FFmpegKit directory with your Package.swift file.

    4 – SPM will now use your local frameworks and won’t redownload them.

    Follow the Package.swift file for using your local frameworks without redownloading them via SPM:

    // swift-tools-version:5.10
    import PackageDescription
    
    let package = Package(
        name: "FFmpegKit",
        platforms: [
            .iOS(.v14),
            .macOS(.v13)
        ],
        products: [
            .library(
                name: "FFmpegKit",
                targets: ["FFmpegKit"]
            )
        ],
        targets: [
            .binaryTarget(
                name: "FFmpegKit-iOS",
                path: "./ffmpeg-iOS.xcframework"
            ),
            .binaryTarget(
                name: "FFmpegKit-macOS",
                path: "./ffmpeg-macOS.xcframework"
            )
        ]
    )
    

    Question 2: Why SPM Chooses ffmpegkit.framework Over Others?

    SPM uses the framework specified by the target platform and architecture (iOS/macOS). The framework paths you specify in Package.swift dictate which framework SPM will use for each platform.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search