'Link to fat Static Library inside Swift Package
I’m trying to build a Swift Package that wraps a fat static library written in C: libndi_advanced_ios.a from NewTek's Apple Advanced NDI SDK.
I am having trouble linking the pre-compiled library (only headers files and .a binary package is available) to my Swift Package. I have done a lot of research and tried different solutions, but none of them worked. Here is a quick list:
Cannot bundle in an XCFramework because libndi_advanced_ios.a supports multiple platforms (arm_v7, i386, x86_64, arm64) and
xcodebuild -create-xcframework
return the errorbinaries with multiple platforms are not supported
(this solution is discussed on Swift Forums too);Using
.linkedLibrary
intargets
as suggested on SPM Documentation (that is outdated) gives the warningsystem packages are deprecated; use system library targets instead
, and I don’t even remember if it builds successfully;Playing around with different flags and settings (like
linkerSettings
) has not been successful. Maybe I just missed the right combination.
I can link dozens of Stackoverflow's questions and other posts that didn’t help, but it will be useless (a, b, c).
At the moment I have this configuration:
With Package.swift
that contains the following code:
let package = Package(
name: "swift-ndi",
platforms: [.iOS(.v12)],
products: [
.library(
name: "swift-ndi",
targets: ["swift-ndi"])
],
dependencies: [],
targets: [
.target(name: "CiOSNDI", path: "Libraries"),
.target(
name: "swift-ndi",
dependencies: ["CiOSNDI"]),
.testTarget(
name: "swift-ndiTests",
dependencies: ["swift-ndi"]),
]
)
You can find the whole project at alessionossa/swift-ndi.
The only result at the moment are some warnings and the module CiOSNDI do not build:
I tried also .systemLibrary(name: "CiOSNDI", path: "Libraries/"),
with this configuration: alessionossa/swift-ndi/tree/systemLibrary; but I get these errors:
NOTE
NDI_include
is actually an alias/symbolic link to /Library/NDI Advanced SDK for Apple/include
, while NDI_iOS_lib
points to /Library/NDI Advanced SDK for Apple/lib/iOS
.
I always cleaned build folder after changes to Package.swift
.
UPDATE 10/01/2022: libndi_advanced_ios.a requires libc++.tbd. That can be easy linked in an app in Build Phases -> Link Binary With Libraries, but I don’t know how to link in a Swift Package.
Solution 1:[1]
Binary targets need to specified with .binary_target
. See the docs and example here.
An example of a static library wrapped in an .xcframework
looks like this from the file command:
$ file GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/GoogleAppMeasurement
GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/GoogleAppMeasurement: Mach-O universal binary with 2 architectures: [arm_v7:current ar archive] [arm64]
GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/GoogleAppMeasurement (for architecture armv7): current ar archive
GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/GoogleAppMeasurement (for architecture arm64): current ar archive
One way to create the .xcframework
file is to use Firebase's ZipBuilder that creates a .xcframework
files from static libraries that are specified with a CocoaPods podspec file.
Solution 2:[2]
I also needed to add the NDI SDK as a Swift package dependency in my app.
I found a couple of approaches that worked:
You can create an XCFramework bundle by extracting a thin arm64 library from the universal library:
lipo "/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a" -thin arm64 -output "$STAGING_DIRECTORY/libndi.a"
Then create an XCFramework bundle:
xcodebuild -create-xcframework -library "$STAGING_DIRECTORY/libndi.a" -headers "/Library/NDI SDK for Apple/include" -output "$STAGING_DIRECTORY/Clibndi.xcframework"
I ended up not using this approach because hosting the XCFramework as a downloadable binary release on GitHub required me to make my repo public (see this issue).
Instead I am using a system library target, in my Package.swift:
targets: [
.target(
name: "WrapperLibrary",
dependencies: ["Clibndi"],
linkerSettings: [
.linkedFramework("Accelerate"),
.linkedFramework("VideoToolbox"),
.linkedLibrary("c++")
]),
.systemLibrary(name: "Clibndi")
]
Then, I have WrapperLibrary/Sources/Clibndi/module.modulemap that looks like:
module Clibndi {
header "/Library/NDI SDK for Apple/include/Processing.NDI.Lib.h"
link "ndi_ios"
export *
}
Finally, my application target (part of an Xcode project, not a Swift package) depends on WrapperLibrary, and I had to add "/Library/NDI SDK for Apple/lib/iOS" (including the quotation marks) to "Library Search Paths" in the "Build Settings" tab.
As an alternative to modifying the application target build settings, you could add a pkg-config file to a directory in your pkg-config search paths. For example, /usr/local/lib/pkgconfig/libndi_ios.pc:
NDI_SDK_ROOT=/Library/NDI\ SDK\ for\ Apple
Name: NDI SDK for iOS
Description: The NDI SDK for iOS
Version: 5.1.1
Cflags: -I${NDI_SDK_ROOT}/include
Libs: -L${NDI_SDK_ROOT}/lib/iOS -lndi_ios
Then use .systemLibrary(name: "Clibndi", pkgconfig: "libndi_ios")
in your package manifest. I found this less convenient for users than just adding the setting to my application target, however.
Ideally you could add the NDI SDK's dependency library and frameworks to the pkg-config file as well (Libs: -L${NDI_SDK_ROOT}/lib/iOS -lndi_ios -lc++ -framework Accelerate -framework VideoToolbox
), but it appears there is a bug in Swift's pkg-config parsing of -framework
arguments, so I filed a bug: SR-15933.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |