Skip to content

Instantly share code, notes, and snippets.

@zwaldowski
Last active September 2, 2021 14:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zwaldowski/30f8f132e2b0fcef2381925146666a7a to your computer and use it in GitHub Desktop.
Save zwaldowski/30f8f132e2b0fcef2381925146666a7a to your computer and use it in GitHub Desktop.

Repackaging a Fat Static Library as an xcframework

Consider this directory tree from a vendor:

OwningTheLibs/
  OwningTheLibs.a
  include/
    OwningTheLibs/
      OwningTheLibs.h
      module.modulemap

A vendor will probably hand you this and say "this supports iOS and iOS simulator". As of 2020 that is no longer true, but we don't need to get in to that right now.

Run the lipo tool on the .a to get a check for its contents:

~ lipo -info OwningTheLibs/libOwningTheLibs.a
Architectures in the fat file: OwningTheLibs/libOwningTheLibs.a are: x86_64 arm64

If you see Non-fat file: OwningTheLibs/libOwningTheLibs.a instead, stop. You cannot proceed.

Thin the binary using lipo:

~ mkdir -p simulator
~ lipo -thin x86_64 libOwningTheLibs.a -output simulator/libOwningTheLibs.a
~ mkdir -p device
~ lipo -thin arm64 libOwningTheLibs.a -output device/libOwningTheLibs.a

Create the xcframework:

$ xcodebuild -create-xcframework \
   -library simulator/libOwningTheLibs.a \
   -headers include \
   -library device/libOwningTheLibs.a \
   -headers include \
   -output OwningTheLibs.xcframework

Note: If the vendor gave you debug symbols or Bitcode maps — which is unlikely, let's be honest - add -debug-symbols after any -headers. It will need to be repeated multiple times. See xcodebuild -create-xcframework -help for more details.

The resulting xcframework will look like this:

OwningTheLibs.xcframework/
  ios-x86_64-simulator/
    Headers/
      OwningTheLibs/
        OwningTheLibs.h
        module.modulemap
    libOwningTheLibs.a
  ios-arm64/
    Headers/
      OwningTheLibs/
        OwningTheLibs.h
        module.modulemap
    libOwningTheLibs.a

The resulting xcframework can be dragged into Xcode into the "Frameworks, Libraries, and Embedded Content" of a target, or embedded into a Swift package:

// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "OwningThePackages",
    platforms: [ .iOS(.v14) ],
    products: [
        .library(
            name: "OwningThePackages",
            targets: ["OwningThePackages"]),
    ],
    targets: [
        .target(
            name: "OwningThePackages",
            dependencies: ["OwningTheLibs"]),
        .binaryTarget(
            name: "OwningTheLibs",
            path: "OwningTheLibs.xcframework")
    ]
)

Making a modulemap for Swift if they didn't provide you with one

Use the following template:

module OwningTheLibs {
    umbrella header "OwningTheLibs.h"
    export *
    module * { export * }
}

Drop that into the include/OwningTheLibs directory to enable import OwningTheLibs in Swift.

If the library is less like an ObjC project and more like a C project, you may have multiple header entries instead:

module OwningTheLibs {
    header "one.h"
    header "two.h"
    export *
    module * { export * }
}

If the vendor says things like "you must link against SystemConfiguration" or "you must link against libz", you can also do that:

module OwningTheLibs {
    umbrella header "OwningTheLibs.h"
    export *
    module * { export * }
    link "z"
    link framework "SystemConfiguration"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment