Link to fat Static Library inside Swift Package - ios

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 error binaries with multiple platforms are not supported (this solution is discussed on Swift Forums too);
Using .linkedLibrary in targets as suggested on SPM Documentation (that is outdated) gives the warning system 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.

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.

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.

Related

iOS swift app build fails with cyclic dependency error in Xcode 14+

I have a swift iOS app with two static libraries - lib1 and lib2 (say). Lib2 has a dependency on Lib1 (i.e it imports Lib1 to use its types). Lib1 and Lib2 are set as dependencies for the AppTarget (when built, results in the .app file).
I get the following cyclic dependency error when building Lib1. Similar error is observed when building the other targets.
SwiftDriverJobDiscovery normal x86_64 Compiling <FileName1>.swift (in target '<Lib1>' from project '<ProjectName>')
error: Cycle inside <Lib1>; building could produce unreliable results.
Cycle details:
→ Target '<Lib1>': Libtool /Users/<user-name>/<some-path>/<Lib1>.a normal
○ Target '<Lib1>' has Swift tasks not blocking downstream targets
○ Target '<Lib1>': SwiftGeneratePch normal x86_64 Compiling bridging header
○ Target '<Lib1>': SwiftCompile normal x86_64 Compiling <FileName2>.swift /Users/<user-name>/<some-path>/<Filename2>.swift
○ Target '<Lib1>': SwiftGeneratePch normal x86_64 Compiling bridging header
Raw dependency cycle trace:
target: ->
node: <all> ->
command: <all> ->
node: /Users/<user-name/<some-path>/<Lib1>.a -> command: P1:target-<Lib1>-6d14b29d8d3402955e18e7b7c2cd5bd8502d5dd7097f7536813aba73cac1c1d5-:Debug:Libtool /Users/<user-name>/<some-path>/<Lib1>.a normal ->
node: /Users/<user-name>/<some-path>/x86_64/<FileName3>-8014457a59adc1f8a995a14873eb809b.o ->
command: P0:target-<Lib1>-6d14b29d8d3402955e18e7b7c2cd5bd8502d5dd7097f7536813aba73cac1c1d5-:Debug:SwiftDriver Compilation <Lib1> normal x86_64 com.apple.xcode.tools.swift.compiler ->
CYCLE POINT ->
customTask: <SwiftDriverJob identifier=-4908891831242875468 arch=x86_64 variant=normal job=<PlannedSwiftDriverJob [target(7)]:GeneratePch <Lib1> dependencies=["target(8)", "target(9)", "target(10)"]> isUsingWholeModuleOptimization=false compilerPath=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc> ->
customTask: <SwiftDriverJob identifier=-4908891831242875468 arch=x86_64 variant=normal job=<PlannedSwiftDriverJob [target(8)]:Compile <Lib1> <FileName2>.swift dependencies=["target(7)"]> isUsingWholeModuleOptimization=false compilerPath=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc> ->
customTask: <SwiftDriverJob identifier=-4908891831242875468 arch=x86_64 variant=normal job=<PlannedSwiftDriverJob [target(7)]:GeneratePch <Lib1> dependencies=["target(8)", "target(9)", "target(10)"]> isUsingWholeModuleOptimization=false compilerPath=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc>
In the above error message, FileName1 and FileName2 are swift files that invoke C++ methods using an intermediate ObjC++ bridge layer. I have two files named FileName3 but different extensions - .swift and .mm. Since, FileName2 is referred as .o here, I think it is referring to FileName3.mm (i.e the ObjC++ file).
I have checked the code thoroughly for any form of cyclic dependency (like, two classes depending on each other - as mentioned in many stackoverflow posts), but it's all good. What's more, the same build worked on Xcode 13.x. After updating to Xcode 14.x, I'm getting this cyclic dependency error.
But in the last three lines of the error message, you can see a cyclic dependency.
First step: [Target(7)] Generate pch and depends on [target(8), target(9), target(10)]
Second step: [Target(8)] Compile FileName2.swift, depends on [target(7)]
Target(7) is dependent on target(8) and target(8) is dependent on target(7). What is target(7), target(8) etc? Where can I find out what kind of target it exactly is?
I use cmake to set the dependencies and generate the Xcode project.
What is this error? What am I missing here?
I've been stuck for days and any help would be greatly appreciated.
After so much time spent on searching, this is the answer.
Checkout ctietze's answer to this post in Apple forum
defaults write com.apple.dt.XCBuild EnableSwiftBuildSystemIntegration 0
After disabling the new build system, I'm not getting the cyclic dependency error anymore.

Swift package has bitcode disabled

I created a really simple Swift package, that contain only a few Objective-C source files, but when I try to build another project that it, it fail compiling because:
ld: bitcode bundle could not be generated because 'MySwiftPackage.o' was built without full bitcode. All object files and libraries for bitcode must be generated from Xcode Archive or Install build file 'MySwiftPackage.o' for architecture arm64
I also tried to force Bitcode to be enable by adding settings:
cSettings: [.define("OTHER_CFLAGS", to: "-fembed-bitcode")] and .define("ENABLE_BITCODE", to: "YES") with no luck.
How can I identify what is causing the bitcode to be disabled ?
Here is my package.swift:
let package = Package(
name: "MySwiftPackage",
platforms: [.iOS(.v11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "MySwiftPackage",
targets: ["MySwiftPackage"]),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "MySwiftPackage",
publicHeadersPath: "Public",
cSettings: [
.headerSearchPath("Internal")
])
]
)

XCFramework's .swiftinterface breaks compile when imported with Swift Package Manager

Created a Swift Package (// swift-tools-version:5.3.0) for an XCFramework. The package has dependencies on 4 other Swift packages (2 of them using swift-tools-version < 5 and 2 are >= 5.0). It builds and runs in the project's Demo app target. The generated XCFramework copied into a Swift Package repo and hosted on GitHub.
I then create a new project and the Swift Package can be successfully imported into it thru SPM. The dependencies are also imported.
When building the new project, my framework fails to compile due to an error in my framework's generated .swiftinterface file, 'XMLIndexerDeserializable' is not a member type of 'SWXMLHash':
Can anyone shed some light on this failure? Not sure if it an issue with my packaged framework, a config issue with the new project that imports it, or an error due to the difference in the Swift tools version of the dependencies.
Some Background Details On My Config and Process
My Swift Package config:
// swift-tools-version:5.3.0
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [
.iOS(.v9)
],
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"])
],
dependencies: [
.package(name: "CryptoSwift", url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMinor(from: "1.2.0")),
.package(name: "Alamofire", url: "https://github.com/Alamofire/Alamofire", .upToNextMinor(from: "4.8.2")),
.package(name: "AEXML", url: "https://github.com/tadija/AEXML", .upToNextMinor(from: "4.4.0")),
.package(name: "SWXMLHash", url: "https://github.com/drmohundro/SWXMLHash", .upToNextMinor(from: "4.9.0"))
],
targets: [
.binaryTarget(
name: "MyPackage",
path: "MyPackage.xcframework")
]
)
Watched the WWDC 2019 and 2020 sessions on building XCFrameworks/binaries for SPM, along with other various tutorials. Used the following sh script to build the XCFramework that would be used for my hosted Swift Package:
xcodebuild archive \
-scheme MyPackage \
-sdk iphoneos \
-archivePath "./XCFrameworkArchives/ios_devices.xcarchive" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
SKIP_INSTALL=NO
xcodebuild archive \
-scheme MyPackage \
-sdk iphonesimulator \
-archivePath "./XCFrameworkArchives/ios_simulators.xcarchive" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
SKIP_INSTALL=NO
xcodebuild -create-xcframework \
-framework ./XCFrameworkArchives/ios_devices.xcarchive/Products/Library/Frameworks/MyPackage.framework \
-framework ./XCFrameworkArchives/ios_simulators.xcarchive/Products/Library/Frameworks/MyPackage.framework \
-output ./XCFrameworkArchives/MyPackage.xcframework
The XCFramework is copied into my Swift Package repo, pushed up to GitHub and tagged.
I then created a new project, used SPM to add a dependency and pulled in MyPackage along with it's dependencies.
Add code to import MyPackage and make use of it. Attempt to build the project throws the compile error that lead me to ask for help. If you can help and need more info, happy clarify and provide more info if I can.
I manually edited the .swiftinterface files generated in the XCFramework for both the device and simulator versions. I simply replaced all occurrences of the framework's name from those files. So changing,
SWXMLHash.XMLIndexerDeserializable -> XMLIndexerDeserializable
solved the issue for me.
Also, look at the latest comments to my question, this looks like a known issue. Going through some of those links and bug issues, my solution common, but is not the "recommended" solution. Recommendation is to not name your framework the same as the classes within. In my situation, it is a Swift package dependency I imported, so I don't have control over that.

Import Kotlin/Native framework in Cocoapod

I'm trying to add a vendored framework built with Kotlin/Native in a private CocoaPod but I get an error:
I have generated an iOS framework with Kotlin/Native.
I copy the framework folder (compiled/generated by Konan) into my custom pod folder
In the podspec, I add the framework path in the "vendored_frameworks" list
I launch pod repo push myCocoapodsRepo myProject.podspec --verbose"
I receive an error :
[iOS] xcodebuild: fatal error: lipo: input file (/Users/jeandaube/Library/Developer/Xcode/DerivedData/App-auugdpsmbbpvarfzghxatkvwftsn/Build/Products/Release-iphonesimulator/App.app/Frameworks/MyProject.framework/MyProject) must be a fat file when the -remove option is specified
Should I somehow change the format of how I export the framework with Konan in a first place?
You're getting the error because, by default, Kotlin Native only produces binaries for a single architecture. CocoaPods than fails when it tries to treat it as a "fat" binary with multiple architectures. Since you'll need multiple architectures anyway (at least arm64 for the device and x86_64 for the simulator) the approach I'm using is to create both architectures and then merge them with lipo The resultant fat framework can then be vended by CocoaPods or just drag/drop installed in Xcode.
def outputDirectory = "$export_dir/$projectName/$projectVersion"
def outputFramework = "$outputDirectory/${projectName}.framework"
konanArtifacts {
// Declare building into a framework, build arm64 for device, x64 for simulator
framework(projectName, targets: ['ios_arm64', 'ios_x64' ]) {
// The multiplatform support is disabled by default.
enableMultiplatform true
}
}
// combine original arm64 and x64 libraries into a single library in
// the exported framework folder
task combineArchitectures(type: Exec, dependsOn: compileKonanLibrary) {
executable 'lipo'
args = [
'-create',
'-arch',
'arm64',
new File(compileKonanLibraryIos_arm64.artifact, 'Library'),
'-arch',
'x86_64',
new File(compileKonanLibraryIos_x64.artifact, 'Library'),
'-output',
"$outputFramework/Library"
]
}
// export the arm64 (doesn't matter which really) framework, skipping
// the library binary itself
task exportFramework(type: Copy, dependsOn: compileKonanLibrary) {
from compileKonanLibraryIos_arm64.artifact
into outputFramework
exclude projectName
finalizedBy combineArchitectures
}
// build a pod spec by copying and updating a template file
task exportPodspec(type: Copy) {
from "Library.podspec"
into outputDirectory
filter {
it.replaceAll('##projectName##', projectName)
.replaceAll('##projectVersion##', projectVersion)
}
}
task export {
dependsOn "exportFramework"
dependsOn "exportPodspec"
}
Kotlin/Native does not produce multiarchitecture (aka fat) binaries (see https://en.wikipedia.org/wiki/Fat_binary) thus an attempt to run lipo on the framework it produces does not make sense.

Can't link MacOS frameworks with CMake

I'm trying to build a subproject with cmake (it's not an xcode project or even an app for iphone, the result is cross-platform console executable, which #includes some inherited from C++ abstract classes, written in objective-c++)
I'm using this guide to link mac os frameworks: http://www.vtk.org/Wiki/CMake:HowToUseExistingOSXFrameworks
and this macro:
macro(ADD_FRAMEWORK fwname appname)
find_library(FRAMEWORK_${fwname}
NAMES ${fwname}
PATHS ${CMAKE_OSX_SYSROOT}/System/Library
PATH_SUFFIXES Frameworks
NO_DEFAULT_PATH)
if( ${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND)
MESSAGE(ERROR ": Framework ${fwname} not found")
else()
TARGET_LINK_LIBRARIES(${appname} ${FRAMEWORK_${fwname}})
MESSAGE(STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}")
endif()
endmacro(ADD_FRAMEWORK)
This is the important part in CMakeLists.txt
project(myprojectname)
........
add_executable(mytarget src/mytarget.cpp)
add_framework(CoreMedia mytarget)
add_framework(CoreVideo mytarget)
add_framework(AVFoundation mytarget)
add_framework(Foundation mytarget)
........
And that's what i have when trying to build:
WARNING: Target "mytarget" requests linking to directory "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CoreMedia.framework". Targets may link only to libraries. CMake is dropping the item.
WARNING: Target "mytarget" requests linking to directory "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CoreVideo.framework". Targets may link only to libraries. CMake is dropping the item.
WARNING: Target "mytarget" requests linking to directory "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/AVFoundation.framework". Targets may link only to libraries. CMake is dropping the item.
WARNING: Target "mytarget" requests linking to directory "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/Foundation.framework". Targets may link only to libraries. CMake is dropping the item.
It actually finds all these frameworks, but can't link, which produces a lot of linker errors. I'm pretty sure that that's the reason because i made a testproj using XCode and it has the same errors till i linked all the needed frameworks.
When i just use
FIND_LIBRARY(COREMEDIA_LIB CoreMedia)
...
then COREMEDIA_LIB is set to NOTFOUND - what's going on? :/
I googled a lot but nothing :( Feeling pretty much lost there.
Got the thing: you have to link NOT the frameworkname.framework folder in the TARGET_LINK_LIBRARIES, but the fwname.framework/fwname file! Now it works that way.
The changed macro is this:
macro(ADD_FRAMEWORK fwname appname)
find_library(FRAMEWORK_${fwname}
NAMES ${fwname}
PATHS ${CMAKE_OSX_SYSROOT}/System/Library
PATH_SUFFIXES Frameworks
NO_DEFAULT_PATH)
if( ${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND)
MESSAGE(ERROR ": Framework ${fwname} not found")
else()
TARGET_LINK_LIBRARIES(${appname} "${FRAMEWORK_${fwname}}/${fwname}")
MESSAGE(STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}")
endif()
endmacro(ADD_FRAMEWORK)
Hope it will be useful for someone...

Resources