Binary Frameworks with SwiftPM

Answering my own question, here's a workaround for the fact that #if targetEnvironment(simulator) doesn't work in a Package.swift file. (Note, this workaround only works when building for iOS device or iOS simulator. A more sophisticated method would need to be used if building for other platforms, but the same principles should still apply.

// swift-tools-version:5.3

import PackageDescription
import Foundation

let base = "${PROJECT_DIR}/../../Carthage/Build/SwiftProtobuf.xcframework"

/// If building for simulator, this will resolve to 
/// "/ios-arm64_i386_x86_64-iphonesimulator", causing simDir to be a
/// valid path. Otherwise it will be an invalid path, which only results
/// in a compiler warning. (If both `simDir` and `realDir` are valid paths,
/// we get a build error, so one of them needs to be invalid.)
let simDir = base + "/ios-arm64_i386_x86_64" + "${LLVM_TARGET_TRIPLE_SUFFIX}"

/// If building for device, this will be unaffected, causing realDir
/// to be a valid path. Otherwise it will be an invalid path, which only
/// results in a compiler warning. (If both `simDir` and `realDir` are valid paths,
/// we get a build error, so one of them needs to be invalid.)
let realDir = base + "/ios-arm64_armv7" + "${LLVM_TARGET_TRIPLE_SUFFIX}"
   
let package = Package(
    name: "REDACTED",
    platforms: [
        .iOS(.v13),
    ],
    products: [
        .library(
            name: "REDACTED",
            type: .dynamic,
            targets: ["REDACTED"]
        ),
    ],
    dependencies: [],
    targets: [
        .target(
            name: "REDACTED"
            ,swiftSettings: [
                .unsafeFlags([
                    "-Fsystem", simDir,
                    "-Fsystem", realDir,
                ]),
            ]
            ,linkerSettings: [
                .linkedFramework("SwiftProtobuf"),
                .unsafeFlags([
                    "-Xlinker", "-rpath", "-Xlinker", simDir,
                    "-Xlinker", "-F", "-Xlinker", simDir,
                    "-Xlinker", "-rpath", "-Xlinker", realDir,
                    "-Xlinker", "-F", "-Xlinker", realDir,
                ])
            ]
        ),
    ]
)

This package can be used by other local packages without complaints from the compiler.

NOTE: link this to an Xcode project, you must wrap it in another local package that has no unsafe flags, where the only source file has @_exported import PACKAGENAME at the top. Then the app will link to this wrapper and get the wrapped version.

This is seems to be a valid workaround for linking Carthage dependency XCFrameworks to local Swift Packages. Haven't tried in production yet but seems to actually work so far. YMMV.

4 Likes