touchlab / KMMBridge

KMMBridge is a set of Gradle tooling that facilitates publishing and consuming pre-built KMM (Kotlin Multiplatform Mobile) Xcode Framework binaries. See https://kmmbridge.touchlab.co/docs to get started.
https://kmmbridge.touchlab.co/
Apache License 2.0
339 stars 20 forks source link

Cocoapod: podspec validation fails when using Kermit-crashlytics #207

Closed lisional closed 1 year ago

lisional commented 1 year ago

Summary

We are unable to publish our xcframework via cocoapods plugin when integrating kermit-crashlytics

Details

Based on KMMBridgeSample we have reproduced the same module hierarchy to build our KMM module. We have a main module for iOS that wraps other modules:

We link everything together in a static xcframework for iOS. and publish it via kmmbridge:

kmmbridge {
    githubReleaseArtifacts(project.version.toString())
    manualVersions()
    cocoapods(
        "<our podspec url>",
        verboseErrors = true
    )
}

We want to add non-fatal reporting for our KMM side and discovered kermit-crashlytics. We are already using Crashlytics on the native side so we want Crashlytics dependency to be resolved when our KMM module is embedded in our app. Following Kermit documentation, packaging our xcframework went well. But we encountered an issue when publishing our podspec.

After publishing our xcframework as a Github Release artifact via KMMBridge, Cocoapods tries to validate it before pushing the new podspec. Validation consists of creating a project, importing the framework and compiling the project. And here we have an issue, since podspec has no reference to Crashlytics, linking fails due to unresolved symbols.

Reproduction

  1. From a copy of KMMBridgeSample integrate kermit-crashlytics
  2. Setup publication via kmmbridge (see sample above)
  3. cocoapod block should be something like that:

    cocoapods {
        name = "KMMBridge-KickStart"
        summary = "KMMBridgeKickStart sample"
        homepage = "https://www.touchlab.co"
        ios.deploymentTarget = "13.5"
        extraSpecAttributes["libraries"] = "'c++', 'sqlite3'"
    
        framework {
            baseName = "KMMBridgeKickStart"
            export(project(":analytics"))
            isStatic = true
            embedBitcode(org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode.DISABLE)
        }
    }
  4. Run ./gradlew pushRemotePodspec -PGITHUB_PUBLISH_TOKEN=<token> -PGITHUB_REPO=<repo> --no-daemon --stacktrace

Expected result

Podspec can be pushed and we can integrate our module in our app via Podfile

Current state

Publication is blocked with the following error:

error:     Undefined symbols for architecture arm64:
error:       "_FIRCLSExceptionRecordNSException", referenced from:
error:           _kfun:co.touchlab.crashkios.crashlytics.CrashlyticsCallsActual#sendFatalException(kotlin.Throwable){} in <OurFramework>(result.o)
error:       "_OBJC_CLASS_$_FIRCrashlytics", referenced from:
error:           objc-class-ref in <OurFramework>(result.o)
error:       "_OBJC_CLASS_$_FIRExceptionModel", referenced from:
error:           objc-class-ref in <OurFramework>(result.o)
error:       "_OBJC_CLASS_$_FIRStackFrame", referenced from:
error:           objc-class-ref in <OurFramework>(result.o)
error:     ld: symbol(s) not found for architecture arm64
error:     clang: error: linker command failed with exit code 1 (use -v to see invocation)
error:
error:   Testing with `xcodebuild`.
error:  -> <OurFramework> (<version>)
error:     - NOTE  | url: The URL (<redacted>) is not reachable.
error:     - WARN  | [iOS] license: Unable to find a license file
error:     - ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code.
error:     - NOTE  | xcodebuild:  note: Using codesigning identity override: -
error:     - NOTE  | [iOS] xcodebuild:  note: Building targets in dependency order
error:     - NOTE  | xcodebuild:  clang: error: linker command failed with exit code 1 (use -v to see invocation)

We can take the xcframework and drop it in our app manually but this is not ideal (framework is versionned in our repo, error prone manipulation)

Possible Fix

I am currently looking around Cocoapods plugin DSL to inject information in the generated podspec. I'm trying to add compiler_flags, or specify Crashlytics dependency without using the pod() command since I do not want Crashlytics embedded in our static framework. Something like that : https://github.com/touchlab/CrashKiOS/issues/46

Another way would be to be able to skip this validation step (--skip-import-validation), but this could hide other issues later on.

Another idea would be to write the podspec manually and to specify a spec.dependency on Crashlytics.

So far I'm stuck and I'm opening this issue to see if I missed something.

Notes

PS1: Thank you for your work, your samples have really helped us along the way so far in setting up a complex KMM module.

PS2: I am opening an issue here since my error is during publication but I may be wrong and should open this in kermit-crashlytics repo instead. Let me know

lisional commented 1 year ago

Follow-up on this, I've decided to drop usage of kermit-crashlytics and go for a mixed approach of

I don't think kermit-crashlytics is a good approach in the end since Crashlytics dependency is not stated in the podspec in the end and breaks delivery via Cocoapods podspec.

kpgalligan commented 9 months ago

Sorry, missed this. Brought up just now. In summary, if anybody is searching for this, you'd probably have to put something like

cocoapods {
        name = "KMMBridge-KickStart"
        // Etc

        pod("FirebaseCrashlytics")

        // Etc
    }

https://kotlinlang.org/docs/native-cocoapods-libraries.html#from-the-cocoapods-repository

Cinterop is rather tricky with 3rd party libs and linking. We used to do what Napier does, which is tell you to pass in lambdas to avoid the whole thing, but then we went down the cinterop route (for better or worse).

kpgalligan commented 9 months ago

Of course, you might have to specify the pod version in Kotlin, which can be frustrating to any iOS team consuming it, because now you're forcing versions on them, but depends on the situation.