I’m struggling with building iOS framework using k...
# kotlin-native
s
I’m struggling with building iOS framework using kotlin native + cinterop. Case: Building iOS framework using ‘kotlin.platform.native’ plugin with an ObjC library (framework) generated for multiple platforms, linked using cinterop. .def file:
Copy code
language = Objective-C
headers = Keychain.h KeychainQuery.h KeychainWrapper.h
compilerOpts = -framework keychainwrapper
linkerOpts = -F/path/ -framework keychainwrapper
excludeDependentModules = true
build.gradle cinterop dependency declaration:
Copy code
dependencies {
        cinterop("KeychainWrapper"){
            packageName "com.company.keychainWrapper"
            includeDirs {
                allHeaders "./KeychainWrapper.framework/Headers"
            }
            linkerOpts "-F${projectDir}"
            compilerOpts "-F${projectDir}"
        }
    }
Problem: After linking Objective-C framework generated for both architectures (x86_64 and arm64) cinterop is letting us to use only the first linked one, so if the x86_64 is the first listed in the build settings - it will work only for that architecture. Effect: I can use the framework on simulator only when using it in my iOS project. The project doesn’t build for the real device Do you have an idea how this can be fixed? (xcode log in comment)
Copy code
ld: warning: ignoring file /Project/ios/keychainwrapper.framework/keychainwrapper, file was built for x86_64 which is not the architecture being linked (arm64): /Project/ios/keychainwrapper.framework/keychainwrapper
ld: warning: ignoring file /Project/sampleapp/app/build/xcode-frameworks/app.framework/app, file was built for x86_64 which is not the architecture being linked (arm64): /Project/sampleapp/app/build/xcode-frameworks/app.framework/app
Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_AppMainPresenter", referenced from:
      objc-class-ref in ViewController.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
s
Could you please provide more details about your setup? What does the following command output?
Copy code
file /Project/ios/keychainwrapper.framework/keychainwrapper
How do you compile Kotlin/Native to x86_64 and arm64?
s
Here’s how I define compilation targets in build.gradle:
Copy code
apply plugin: 'org.jetbrains.kotlin.platform.native'
sourceSets.main {
    kotlin.srcDirs += 'src/main/kotlin'
}

components.main {
    targets = ['ios_arm64', 'ios_x64']
    outputKinds = [KLIBRARY]
    baseName = "kotlinmultiplatformstorage-ios"

    dependencies {
        cinterop("KeychainWrapper"){
            packageName "com.netguru.keychainWrapper"
            includeDirs {
                allHeaders "./KeychainWrapper.framework/Headers"
            }
            linkerOpts "-F${projectDir}"
            compilerOpts "-F${projectDir}"
        }
    }
}

dependencies {
    expectedBy project(':common')
}
file command returns the following metadata:
./ios/keychainwrapper.framework/keychainwrapper: Mach-O 64-bit dynamically linked shared library arm64
maybe I’m doing something wrong while generating keychainwrapper.framework in xcode 🤔
s
It seems so. And other error
ld: warning: ignoring file /Project/sampleapp/app/build/xcode-frameworks/app.framework/app, file was built for x86_64 which is not the architecture being linked (arm64)
may be caused by incorrect configuration too: Xcode refers the framework built for x86_64.
s
Thank you @svyatoslav.scherbina. I’ve managed to fix my
keychainwrapper.framework
in xcode. Now it targets both architectures and the build is successful. 💪 However, I’m experiencing a further problem. I Have a sample app project which uses
kotlin-multiplatform
plugin where I’m adding the successfully built .klib dependency. The sample app project is generating an iOS
.framework
library which I’m linking in xcode to the iOS project. Here’s how I obtain the iOS target in the gradle build script:
Copy code
kotlin {
    targets {
        final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") ? presets.iosArm64 : presets.iosX64
        println(System.getenv('SDK_NAME'))
        fromPreset(presets.android, 'android')
        fromPreset(iOSTarget, 'ios') {
            compilations.main.outputKinds('FRAMEWORK')
        }
    }
The xCode is configured to run the
packForXCode
task which invokes
build
task first when building the iOS app. However, the problem is the
SDK_NAME
environment variable doesn’t exist. Do you have any clue what can be a problem? I have no clue what can I be missing (probably something blocks xcode from setting this variable)
As the result of
SDK_NAME
being null I’m not able to run the sample app on the real device (it works well on emulator)
s
Do you have the Gradle build being kicked off in a script phase of your Xcode project?
s
Which Gradle version do you use?
s
It turned out
SDK_NAME
is set correctly when gradle build command is invoked by xcode. (It was null when build was run on terminal). Another issue was that I needed to force gradle to rebuild the project for the correct target by calling
./gradlew clean
right before
packForXCode
(in xcode). Otherwise gradle doesn’t detect any source code change and doesn’t want to regenerate the
app.framework
when I’m trying to switch between iphone simulator and iphone device targets.
The
./gradlew clean
forces gradle building for the correct target. However, it slows down the building process significantly. I need to wait 5min each time I’m building the iOS app even if I’m not changing the compilation target.
I’m looking for the way to optimise it. It would be ideal not to run
./gradlew clean
each time but only when the
SDK_NAME
variable value change is detected but I don’t find it possible.
I’m using gradle wrapper 4.7
Anyway, thank you for the help so far. The project is built correctly now. Let me know if you have an idea for optimizing the build process
s
Otherwise gradle doesn’t detect any source code change and doesn’t want to regenerate the
app.framework
when I’m trying to switch between iphone simulator and iphone device targets.
@ilya.matveev do you have any ideas?
i
Sorry for the delay with answering. What version of Java do you use? I ask because the issue with switching between targets may be caused by this bug in Gradle: https://github.com/gradle/gradle/issues/6330. In couple of words environment variables (e.g.
SDK_NAME
) may not be passed to a Gradle script executed by the Gradle daemon. This bug is observed on Java 9+ and fixed in Gradle 4.10. So the solution is to use Gradle 4.10 or later. If for some reason it's impossible (e.g. if you use kotlinx libraries with Gradle metadata enabled), this issue can be fixed by disabling Gradle daemon. Just pass the
--no-daemon
flag to Gradle when you run the
packForXcode
task:
Copy code
./gradlew :packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION} -i --no-daemon
As for not rebuilding after changing sources, it looks strange. I didn't manage to reproduce this issue with a playground project based on this tutorial: https://kotlinlang.org/docs/tutorials/native/mpp-ios-android.html. Could you please provide a link to your project?