Jeff Lockhart
02/05/2021, 10:12 PMTijl
02/05/2021, 10:17 PMJeff Lockhart
02/06/2021, 7:48 AM> Task :shared:linkDebugTestIos
e: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors
The /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld command returned non-zero exit code: 1.
output:
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_CBLMutableDocument", referenced from:
objc-class-ref in result.o
"_OBJC_CLASS_$_CBLMutableDictionary", referenced from:
objc-class-ref in result.o
"_OBJC_CLASS_$_CBLMutableArray", referenced from:
objc-class-ref in result.o
"_OBJC_CLASS_$_CBLBlob", referenced from:
objc-class-ref in result.o
ld: symbol(s) not found for architecture x86_64
> Task :shared:linkDebugTestIos FAILED
The frameworksPath I’m providing is relative to the KMM shared module’s build folder to the iOS app’s Pods folder, where the CouchbaseLite.framework folder is: ../../ios/app/Pods/CouchbaseLite-Enterprise/iOS
The other code in the example gives me the error:
Could not create task ':shared:iosTest'.
> Replacing an existing task with an incompatible type is not supported. Use a different name for this task ('iosTest') or use a compatible type (org.gradle.api.DefaultTask)
Not sure what’s causing that, but seems I need to get past the linker error first anyway.
@alex009 if you wouldn’t mind explaining some more about what the process would be to utilize cocoapods dependencies in Kotlin tests, and the steps to get it working, I’d be grateful to you.Tijl
02/06/2021, 10:35 AMFrameworks
next to test.kexe
binary.linkerOpts("-F$frameworksPath")
for every frameworklinkerOpts("-framework", "$frameworkname}")
val copyFrameworksForX64Test by tasks.registering(Copy::class) {
val tree = fileTree("$buildDir/cocoapods/synthetic/iosX64/${project.name.replace('-','_')}/build/Release-iphonesimulator")
from(tree) {
include("**/*.framework/**")
include("**/*.framework.DSYM/**")
include("**/*.bundle")
}
eachFile {
path = path.substring(1).substringAfter("/", path)
}
into ("$buildDir/bin/iosX64/debugTest/Frameworks")
}
val linkDebugTestIosX64 by tasks.getting {
dependsOn(copyFrameworksForX64Test)
}
copies this from an old gradle task I made to do the copying
iosX64 {
binaries {
getTest("DEBUG").apply {
linkerOpts("-ObjC")
linkerOpts("-framework", "someFramework")
linkerOpts("-F$buildDir/bin/iosX64/debugTest/Frameworks")
and from an old build fileKris Wong
02/08/2021, 2:05 PMiosX64("ios") {
compilations {
"test" {
cinterops.create("OHHTTPStubs") {
includeDirs("c_interop/OHHTTPStubs/Headers")
}
}
}
binaries.getTest(DEBUG).linkerOpts("-Lc_interop/OHHTTPStubs")
}
linkerOpts = -lOHHTTPStubs -ObjC
Jeff Lockhart
02/09/2021, 10:15 PMlinkerOpts("-framework", "CouchbaseLite")
got the linking working. Then iosTest
is failing because the library isn’t loaded.
> Task :shared:linkDebugTestIos
> Task :shared:iosTest FAILED
dyld: Library not loaded: @rpath/CouchbaseLite.framework/CouchbaseLite
Referenced from: /.../shared/build/bin/ios/debugTest/test.kexe
Reason: image not found
Child process terminated with signal 6: Abort trap
Do you have any tips for loading the library at test runtime?-rpath
to the frameworks path, which is used to load the library at runtime. Combining it all:
iosX64("ios") {
binaries {
getTest("DEBUG").apply {
val frameworksPath = "${buildDir.absolutePath}/cocoapods/synthetic/IOS/shared/Pods/CouchbaseLite-Enterprise/iOS"
linkerOpts("-F$frameworksPath")
linkerOpts("-rpath", frameworksPath)
linkerOpts("-framework", "CouchbaseLite")
}
}
}
I switched the path to the cocoapods/synthetic/…
path, since that’s contained within the shared module build path and doesn’t depend on my iOS app at all. If I was using more than one framework, it’d be a bit more complicated, since the frameworks would need to be copied into the same directory. But this could be done in combination with setting the -rpath
to that directory as part of the build process.
Thanks again for your help!Tijl
02/10/2021, 8:15 AM-rpath
is a way better method!
reading the docs (of ld
) it seems multiple -rpath
arguments should work too.Kris Wong
02/10/2021, 1:52 PMTijl
02/10/2021, 1:59 PMKris Wong
02/10/2021, 2:02 PMJeff Lockhart
02/10/2021, 10:46 PM-l
, which is slightly different than the framework format from cocoapods, linked with -framework
.
The default rpath
expects the frameworks to be in a folder called Frameworks
in the same directory as the test.kexe
binary build/bin/ios/debugTest
. Now I see that’s where your code is copying the frameworks. So yeah, setting the -rpath
option allows you to reference the frameworks from the original location without needing to copy.
Since -rpath
can be provided multiple times, then seems the cocoapods plugin should be able to append these combination of -F
, -rpath
, and -framework
linker opts to the test binary for each of the cocoapods dependencies added. I haven’t found a YouTrack issue for this. So I created one.Kris Wong
02/11/2021, 1:56 PMBen Deming
02/12/2021, 12:14 AMbinaries {
getTest("DEBUG").apply {
// Map of pod names to their `module_name`s
val regularPodToModuleNameMap = mapOf(
"CocoaLumberjack" to null,
"Split" to null,
"DatadogSDKObjc" to "DatadogObjc",
"DatadogSDK" to "Datadog"
)
// Map of vendored pod names to their `module_name`s (maybe redundant, they might need to be the same?)
val vendoredPodToModuleNameMap = mapOf("TwilioChatClient" to null)
// common is the name of our project, substitute accordingly
val commonSyntheticPath = "${buildDir.absolutePath}/cocoapods/synthetic/IOS/common"
val regularFrameworksPath = "$commonSyntheticPath/build/Release-iphonesimulator"
val vendoredFrameworkPath = "$commonSyntheticPath/Pods"
regularPodToModuleNameMap.forEach { entry ->
val podDirectoryName = entry.key
val moduleName = entry.value ?: entry.key
val frameworkPath = "$regularFrameworksPath/$podDirectoryName"
linkerOpts("-framework", moduleName)
linkerOpts("-F$frameworkPath")
linkerOpts("-rpath", frameworkPath)
}
vendoredPodToModuleNameMap.forEach { entry ->
val podDirectoryName = entry.key
val moduleName = entry.value ?: entry.key
val frameworkPath = "$vendoredFrameworkPath/$podDirectoryName"
linkerOpts("-framework", moduleName)
linkerOpts("-F$frameworkPath")
linkerOpts("-rpath", frameworkPath)
}
// Don't forget framework flags for system frameworks any pods depend on
linkerOpts("-framework", "UIKit")
}
}
Could use some DRYing up but may be helpful nevertheless.Tijl
02/12/2021, 3:04 PM