Skip to content

Instantly share code, notes, and snippets.

@jaredsinclair
Forked from zwaldowski/repackage.md
Created September 1, 2021 21:34
Show Gist options
  • Save jaredsinclair/e4afa65bf16cc28ce3240625afce0d1d to your computer and use it in GitHub Desktop.
Save jaredsinclair/e4afa65bf16cc28ce3240625afce0d1d 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

The vendor will probably say "this supports iOS and iOS simulator". As of 2019/2020 this 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