Hey all. I am trying to use the <StripeTerminal iO...
# kotlin-native
a
Hey all. I am trying to use the StripeTerminal iOS xcframework in my kmm module. I've manually placed the xcframework in the kmm module in an
iOSLibs
directory. The klib for the module successfully compiles and I'm able to reference and use the framework in my iOS specific kotlin classes but the build breaks down during the
linkDebugTestIos
step in the
iosTest
gradle build task. When I try to specify the framework and framework path in the
linkerOpts
in my
.def
file, I get a linking error saying the
StripeTerminal
framework cannot be found (even though I'm certain it's there - tested by using the absolute path locally for the framework paths). I've tried specifying the
implementation fileTree(dir: <dir>, include: <binary>)
in the
iosTest { dependencies { ... } }
with no luck. Not sure how I can properly link the framework to the test test. Am I missing something with regards to linking a native iOS framework for use in a KMM module? Any suggestions/ideas are greatly appreciated
j
I'm not sure specifically for your case, but I do know from my experience a limitation of linker options in the .def file is that the paths can't be relative, which doesn't work for binaries in your repo that need to use a path relative to the project location. I've specified linker options in Gradle instead, in
KotlinNativeTarget
binary.linkerOpts
, to be able to get the absolute path relative to
projectDir
.
a
Thanks for the message! I tried your suggestion. When my
build.gradle
looks like the following:
Copy code
kotlin {
    def iosTarget = kotlin.targets.ios
    def iosDevice = findProperty("kn.ios.platform") == "iphoneos" ?: false
    def target = iosDevice ? "ios-arm64" : "ios-arm64_x86_64-simulator"
    iosTarget.compilations.getByName("main").cinterops.create("StripeTerminal")
    iosTarget.binaries {
        framework("StripeWrapper") {
            linkerOpts += ["-F${projectDir}/iOSLibs/StripeTerminal.xcframework/$target/StripeTerminal.framework"]
            linkerOpts += ["-framework", "StripeTerminal"]
        }
    }
    sourceSets {
        iosTest {
            dependencies {}
        }
I get the error:
Copy code
Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_SCPPaymentMethod", referenced from:
      objc-class-ref in result.o
ld: symbol(s) not found for architecture arm64

Execution failed for task ':PointOfSale-StripeWrapper:linkDebugTestIos'.
> Compilation finished with errors
When I add a filepath declaration in the
iosTest
dependencies like the following:
Copy code
kotlin {
    def iosTarget = kotlin.targets.ios
    def iosDevice = findProperty("kn.ios.platform") == "iphoneos" ?: false
    def target = iosDevice ? "ios-arm64" : "ios-arm64_x86_64-simulator"
    iosTarget.compilations.getByName("main").cinterops.create("StripeTerminal")
    iosTarget.binaries {
        framework("StripeWrapper") {
            linkerOpts += ["-F${projectDir}/iOSLibs/StripeTerminal.xcframework/$target/StripeTerminal.framework"]
            linkerOpts += ["-framework", "StripeTerminal"]
        }
    }
    sourceSets {
        iosTest {
            dependencies {
                implementation files("${projectDir}/iOSLibs/StripeTerminal.xcframework/$target/StripeTerminal.framework")
            }
        }
I get the error (I verified in my filesystem that the framework is at that exact path):
Copy code
e: Could not find "/Users/vastopa/src/github.com/Shopify/pos-next-react-native/multiplatform/PointOfSale-StripeWrapper/iOSLibs/StripeTerminal.xcframework/ios-arm64_x86_64-simulator/StripeTerminal.framework" in [/Users/vastopa/src/github.com/Shopify/pos-next-react-native/android, /Users/vastopa/.konan/klib, /Users/vastopa/.konan/kotlin-native-prebuilt-macos-aarch64-1.7.10/klib/common, /Users/vastopa/.konan/kotlin-native-prebuilt-macos-aarch64-1.7.10/klib/platform/ios_simulator_arm64]

Execution failed for task ':PointOfSale-StripeWrapper:compileTestKotlinIos'.
> Compilation finished with errors
Seem to be stuck in a circle of either not being able to find the framework or the framework being present but linking doesn't work for whatever reason. Any further suggestions/thoughts?
j
Be sure to include the
-rpath
linker option for the runtime path for the dynamic framework.
a
Ah yea, I remember seeing that in a github issue. I tried adding it in in my
StripeTerminal.def
as
linkerOpts = -rpath @loader_path/Frameworks
, as well as in
Copy code
framework("StripeWrapper") {
    linkerOpts += ["-rpath", "@loader_path/Frameworks"]
    linkerOpts += ["-F${projectDir}/iOSLibs/StripeTerminal.xcframework/$target/StripeTerminal.framework"]
    linkerOpts += ["-framework", "StripeTerminal"]
}
Still no luck. Do I need to do something like this comment (although having a hardtime to determine how exactly to fill in the blanks there). One other thing I'm noticing is I can't seem to locate the framework output for
StripeWrapper
. It should be in
build/bin/ios
path but I guess it's not there because the linking step fails?
j
I'm just using the same absolute path as the
-F
option for
-rpath
.
a
If I'm understanding correctly, the
linkerOpts
should look as follows?
Copy code
iosTarget.binaries {
        framework("StripeWrapper") {
            linkerOpts += ["-rpath", "${projectDir}/iOSLibs/StripeTerminal.xcframework/$target/StripeTerminal.framework"]
            linkerOpts += ["-F${projectDir}/iOSLibs/StripeTerminal.xcframework/$target/StripeTerminal.framework"]
            linkerOpts += ["-framework", "StripeTerminal"]
        }
    }
If so, unfortunately, still seeing the error
Copy code
Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_SCPPaymentMethod", referenced from:
      objc-class-ref in result.o
ld: symbol(s) not found for architecture arm64

Execution failed for task ':PointOfSale-StripeWrapper:linkDebugTestIos'.
> Compilation finished with errors
PS - the reason it's that particular class in the error is because I've set up a test to reference the StripeTerminal SDK to test the linking. Without the reference to
SCPPaymentMethod
, the build/linking passes:
Copy code
package com.shopify.pos.stripewrapper

import StripeTerminal.SCPPaymentMethod
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertEquals

class StripePaymentModelsTest {
    @Test
    fun `fake test to testing fixing linking`() = runBlocking {
        val paymentMethod = SCPPaymentMethod()
        assertEquals(paymentMethod, paymentMethod)
    }
}
j
This is close to what I have, except remove the
/StripeTerminal.framework
from the end of both paths.
a
Unfortunately, I end up with the same result. Do you know, am I supposed to be seeing a
StripeWrapper.framework
output in my build products or does the
iosTest
task only create a
.kexe
(all I'm seeing is the latter)
j
No, the test binary doesn't build your library as a .framework. You'll also need to specify the linker options for the test binary:
Copy code
binaries {
    getTest(DEBUG).linkerOpts(...)
}
a
wow. it worked! thought this day would never come 😅 can't thank you enough for your help!!
j
Great, glad to help. Linker options can definitely be tricky.
a
it was the framework path excluding the
StripeTerminal.framework
, the
getTest
, and the
-rpath
all combined which I wasn't getting all right in all my testing. happy to be getting more familiar with the intricacies of gradle!
j
You likely won't want the hard-coded absolute
-rpath
linker option in the framework build you're distributing, since that path won't exist for consumers of the framework. But it works well for tests.
a
I'm using
projectDir
, and yea, I only had to add linkerOpts to the test target. thanks for the tip!
👍 1