Link to a Precompiled Static C Library in a Swift Library Package

I want to build a Swift library package that uses modified build of OpenSSL and Curl.

I have already statically compiled both and verified I can use them in an Objective-C framework on my target platform (iOS & iOS Simulator). I'm using XCFramework files that contain the static library binaries and headers:

openssl.xcframework/
    ios-arm64/
        openssl.framework/
            Headers/
                [...]
            openssl
    ios-arm64_x86_64-simulator/
        openssl.framework/
            Headers/
                [...]
            openssl
    Info.plist

I'm not sure how I'm supposed to set up my Swift package to import these libraries.

I can use .systemLibrary but that seems to use the embedded copies of libssl and libcurl on my system, and I can't figure out how to use the path: parameter to that.

I also tried using a .binaryTarget pointing to the XCFramework files, but that didn't seem to work as there is no module generated and I'm not sure how to make one myself.

At a basic high level, this is what I'm trying to accomplish:

where libcrypto & libssl come from the provided openssl.xcframework file, and libcurl from curl.xcframework

Answered by ecnepsnai in 794662022

Thank you so much, Quinn! I genuinely owe you for how often you unblock my from complicated issues :)

I agree that it would be best to let Xcode do the heavy lifting, and perhaps I will investigate that in the future - for now, since I already have a build pipeline set up for OpenSSL and Curl, I went with your suggestion of looking at what Xcode does and making a similar structure.

I can confirm that once I defined the module map within the platform-specific frameworks I can now use the libraries in Swift, both a Swift framework and a Swift package.

My go-to reference for integrating XCFrameworks into Swift packages is the Swift Evolution proposal itself, namely SE-0272. It looks like you tried that, but it’s not clear what actually went wrong. How did you set this up? And what actually failed?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn, let me elaborate.

When using a binary target, my Package.swift file looks like:

/Package.swift:

// swift-tools-version: 5.10
import PackageDescription

let package = Package(
    name: "MyLibrary",
    products: [
        .library(
            name: "MyLibrary",
            targets: ["MyLibrary"]),
    ],
    targets: [
        .target(
            name: "MyLibrary",
            dependencies: ["OpenSSL", "Curl"]
        ),
        .binaryTarget(name: "OpenSSL", path: "openssl.xcframework"),
        .binaryTarget(name: "Curl", path: "curl.xcframework"),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary"]),
    ]
)

When I try to import either OpenSSL or Curl within any source files for MyLibrary it can't find those modules, for example:

/Sources/MyLibrary/Versions.swift:

import Foundation
import OpenSSL // No such module 'OpenSSL'
import Curl // No such module 'Curl'

public final class Versions {
    public static func openssl() -> String {
        return OPENSSL_VERSION_STR
    }

    public static func curl() -> String {
        return LIBCURL_VERSION
    }
}

I'm assuming that I need to define a module map file, but that doesn't seem to have any effect. If I create a module map for OpenSSL:

Sources/MyLibrary/openssl.modulemap:

module OpenSSL [system] {
    header "opensslv.h"
    link "ssl"
    link "crypto"
    export *
}

It doesn't seem to do anything, as I get the same error as above. In fact, I'm not even sure this file is being used at all, as if I fill it with gibberish, I don't get an expected syntax error.

I'm assuming that I need to define a module map file

Yeah, that’s definitely true.

There are a couple of things that I’d do to investigate this. First, divorce yourself from the many complexities of OpenSSL. Create a trivial C framework using Xcode, wrap that up in an XCFramework using the standard instructions, and then import that into Xcode. Can you get that to work?

Next, wrap your working XCFramework into a Swift package and import that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks Quinn,

I tried what you suggested and made a barebones C library that contains a single header and exports 1 method. I exported that into an xcframework.

If I make an iOS Framework using Objective-C then I can import that xcframework and use it in my code. The only modification I needed to make was to set the "framework search path" build setting.

However, if I try to make the same library using Swift, I can't use the xcframework within my code. I think it's the same issue as before where there's no module defined. I tried to make a module map file, but it again doesn't seem to even be recognized by the build.

I put everything up on a GitHub repo to make reviewing easier: https://github.com/ecnepsnai/Temp-Swift-Library-Issue

"ExampleLibrary" is the C library and "MyFramework" is the framework I'm trying to use it in.

Accepted Answer

Your ExampleLibrary target is a static library target. Static libraries are a pain because of the weak binding between the library and its headers. My earlier advice was “Create a trivial C framework”, and I stand by that.

Note that a framework target will build a module map for you, based on the Defines Module build setting. For example:

% find FFF758816.framework 
FFF758816.framework
…
FFF758816.framework/Headers
FFF758816.framework/Headers/Example.h
FFF758816.framework/Headers/FFF758816.h
FFF758816.framework/FFF758816
FFF758816.framework/Modules
FFF758816.framework/Modules/module.modulemap
FFF758816.framework/Info.plist

Now, I’m sure it’s possible to do the module map thing with a static library target, but I’m a big fan of getting Xcode to do the heavy lifting for me. And even if you can’t build your third-party libraries using Xcode, you can still look at what Xcode builds, make sure it works for your clients, and then tweak your library build process to generate a similar structure.

Two other things:

  • Xcode now support mergeable libraries, which allow you to build a framework that can be deployed as either a static library or a dynamic library. See WWDC 2023 Session 10268 Meet mergeable libraries.

  • Xcode 15 finally introduced official support for static frameworks. That might be the best path forward for your third-party libraries, because their native build system is set up to build a static library.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you so much, Quinn! I genuinely owe you for how often you unblock my from complicated issues :)

I agree that it would be best to let Xcode do the heavy lifting, and perhaps I will investigate that in the future - for now, since I already have a build pipeline set up for OpenSSL and Curl, I went with your suggestion of looking at what Xcode does and making a similar structure.

I can confirm that once I defined the module map within the platform-specific frameworks I can now use the libraries in Swift, both a Swift framework and a Swift package.

Link to a Precompiled Static C Library in a Swift Library Package
 
 
Q