From 8f26b52fdc29cccf55ff7ede00a5acd94d9ea826 Mon Sep 17 00:00:00 2001 From: Oskar Persson Date: Fri, 13 Dec 2024 21:04:50 +0100 Subject: [PATCH] Revert "#747 worked solution with static library inside" This reverts commit f308f1bf37984846c0c40478a5feeebe0ed5e086. --- gradle.properties | 1 - ...pple-bundle-searcher-convention.gradle.kts | 164 ------------------ resources/build.gradle.kts | 1 - .../src/appleMain/def/bundleSearcher.def | 5 - .../moko/resources/utils/NSBundleExt.kt | 26 +-- .../appleMain/objective-c/MRResourcesBundle.h | 9 - .../appleMain/objective-c/MRResourcesBundle.m | 13 -- 7 files changed, 13 insertions(+), 206 deletions(-) delete mode 100644 resources-build-logic/src/main/kotlin/apple-bundle-searcher-convention.gradle.kts delete mode 100644 resources/src/appleMain/def/bundleSearcher.def delete mode 100644 resources/src/appleMain/objective-c/MRResourcesBundle.h delete mode 100644 resources/src/appleMain/objective-c/MRResourcesBundle.m diff --git a/gradle.properties b/gradle.properties index d149d3a9..fd97ac0e 100755 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,6 @@ kotlin.mpp.stability.nowarn=true kotlin.mpp.androidGradlePluginCompatibility.nowarn=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.applyDefaultHierarchyTemplate=false -kotlin.mpp.enableCInteropCommonization=true org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true diff --git a/resources-build-logic/src/main/kotlin/apple-bundle-searcher-convention.gradle.kts b/resources-build-logic/src/main/kotlin/apple-bundle-searcher-convention.gradle.kts deleted file mode 100644 index 564e19ab..00000000 --- a/resources-build-logic/src/main/kotlin/apple-bundle-searcher-convention.gradle.kts +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2024 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. - */ - -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.konan.target.KonanTarget - -plugins { - id("org.jetbrains.kotlin.multiplatform") -} - -/* -This code ensures that the Bundle in an iOS application, built with Kotlin Multiplatform (KMP), can be correctly -located at runtime. The issue arises because Kotlin doesn’t allow direct lookup of a Bundle by a class from -Objective-C. To resolve this, a static library written in Objective-C was created and automatically included in the -Kotlin Framework during the build process. This library contains a class used to locate the required Bundle. - -Key steps performed by the code: - -1. Handling Apple targets in KMP: - The code automatically configures the build for Apple platforms only (iOS, macOS, tvOS, watchOS). -2. Compiling and linking the static library: - - clang is used to compile the source file MRResourcesBundle.m into an object file. - - The object file is linked into a static library (libMRResourcesBundle.a) using the ar utility. -3. Integrating the static library into the Kotlin Framework: - - A C-interop is created, enabling Kotlin to interact with the Objective-C code from the library. - - The C-interop task is configured to depend on the compilation and linking tasks, ensuring the library is ready for - use during the build process. -4. Support for multiple Apple platforms: - - The code adapts the build process for specific Apple SDKs and architectures by using helper functions getAppleSdk - and getClangTarget. -5. Retrieving the SDK path: - The xcrun utility is used to dynamically fetch the SDK path required by clang. - -What does this achieve? - -As a result, a Kotlin Multiplatform application for iOS, macOS, tvOS, or watchOS can correctly locate the Bundle -containing resources by leveraging standard Apple APIs wrapped in the static library. This process is fully automated -during the project build, requiring no manual intervention from the developer. - -Bundle search logic: -resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt -*/ - -kotlin.targets - .withType() - .matching { it.konanTarget.family.isAppleFamily } - .configureEach { - val sdk: String = this.konanTarget.getAppleSdk() - val target: String = this.konanTarget.getClangTarget() - - val sdkPath: String = getSdkPath(sdk) - - val libsDir = File(buildDir, "moko-resources/cinterop/$name") - libsDir.mkdirs() - val sourceFile = File(projectDir, "src/appleMain/objective-c/MRResourcesBundle.m") - val objectFile = File(libsDir, "MRResourcesBundle.o") - val libFile = File(libsDir, "libMRResourcesBundle.a") - val kotlinTargetPostfix: String = this.name.capitalize() - - val compileStaticLibrary = tasks.register("mokoBundleSearcherCompile$kotlinTargetPostfix", Exec::class) { - group = "moko-resources" - - commandLine = listOf( - "clang", - "-target", - target, - "-isysroot", - sdkPath, - "-c", - sourceFile.absolutePath, - "-o", - objectFile.absolutePath - ) - outputs.file(objectFile.absolutePath) - } - val linkStaticLibrary = tasks.register("mokoBundleSearcherLink$kotlinTargetPostfix", Exec::class) { - group = "moko-resources" - - dependsOn(compileStaticLibrary) - - commandLine = listOf( - "ar", - "rcs", - libFile.absolutePath, - objectFile.absolutePath - ) - outputs.file(libFile.absolutePath) - } - - compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) { - val bundleSearcher by cinterops.creating { - defFile(project.file("src/appleMain/def/bundleSearcher.def")) - - includeDirs("$projectDir/src/appleMain/objective-c") - extraOpts("-libraryPath", libsDir.absolutePath) - } - - tasks.named(bundleSearcher.interopProcessingTaskName).configure { - dependsOn(linkStaticLibrary) - } - } - } - -fun KonanTarget.getAppleSdk(): String { - return when (this) { - KonanTarget.IOS_ARM32, - KonanTarget.IOS_ARM64 -> "iphoneos" - - KonanTarget.IOS_SIMULATOR_ARM64, - KonanTarget.IOS_X64 -> "iphonesimulator" - - KonanTarget.MACOS_ARM64, - KonanTarget.MACOS_X64 -> "macosx" - - KonanTarget.TVOS_ARM64 -> "appletvos" - - KonanTarget.TVOS_SIMULATOR_ARM64, - KonanTarget.TVOS_X64 -> "appletvsimulator" - - KonanTarget.WATCHOS_ARM32, - KonanTarget.WATCHOS_DEVICE_ARM64 -> "watchos" - - KonanTarget.WATCHOS_ARM64, - KonanTarget.WATCHOS_SIMULATOR_ARM64, - KonanTarget.WATCHOS_X64, - KonanTarget.WATCHOS_X86 -> "watchsimulator" - - else -> error("Unsupported target for selecting SDK: $this") - } -} - -fun KonanTarget.getClangTarget(): String { - return when (this) { - KonanTarget.IOS_ARM32 -> "armv7-apple-ios" - KonanTarget.IOS_ARM64 -> "aarch64-apple-ios" - KonanTarget.IOS_SIMULATOR_ARM64 -> "aarch64-apple-ios-simulator" - KonanTarget.IOS_X64 -> "x86_64-apple-ios-simulator" - - KonanTarget.MACOS_ARM64 -> "aarch64-apple-macosx" - KonanTarget.MACOS_X64 -> "x86_64-apple-macosx" - - KonanTarget.TVOS_ARM64 -> "aarch64-apple-tvos" - KonanTarget.TVOS_SIMULATOR_ARM64 -> "aarch64-apple-tvos-simulator" - KonanTarget.TVOS_X64 -> "x86_64-apple-tvos-simulator" - - KonanTarget.WATCHOS_ARM32 -> "armv7k-apple-watchos" - KonanTarget.WATCHOS_ARM64 -> "arm64_32-apple-watchos" - KonanTarget.WATCHOS_DEVICE_ARM64 -> "aarch64-apple-watchos" - KonanTarget.WATCHOS_SIMULATOR_ARM64 -> "aarch64-apple-watchos-simulator" - KonanTarget.WATCHOS_X64 -> "x86_64-apple-watchos-simulator" - KonanTarget.WATCHOS_X86 -> "i386-apple-watchos" - - else -> error("Unsupported target for selecting clang target: $this") - } -} - -fun getSdkPath(sdk: String): String { - val process = ProcessBuilder("xcrun", "--sdk", sdk, "--show-sdk-path") - .redirectErrorStream(true) - .start() - return process.inputStream.bufferedReader().use { it.readText().trim() } -} diff --git a/resources/build.gradle.kts b/resources/build.gradle.kts index f55b49d3..ba13311f 100644 --- a/resources/build.gradle.kts +++ b/resources/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id("multiplatform-library-extended-convention") id("multiplatform-android-publish-convention") id("apple-main-convention") - id("apple-bundle-searcher-convention") id("detekt-convention") id("javadoc-stub-convention") id("publication-convention") diff --git a/resources/src/appleMain/def/bundleSearcher.def b/resources/src/appleMain/def/bundleSearcher.def deleted file mode 100644 index 1e6af252..00000000 --- a/resources/src/appleMain/def/bundleSearcher.def +++ /dev/null @@ -1,5 +0,0 @@ -# https://kotlinlang.org/docs/native-definition-file.html -language = Objective-C -package = dev.icerock.moko.resources.apple.native -staticLibraries = libMRResourcesBundle.a -headers = MRResourcesBundle.h diff --git a/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt b/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt index 2d18f581..398a17dd 100644 --- a/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt +++ b/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt @@ -4,8 +4,6 @@ package dev.icerock.moko.resources.utils -import dev.icerock.moko.resources.apple.native.ResourcesBundleAnchor -import kotlinx.cinterop.ExperimentalForeignApi import platform.Foundation.NSBundle import platform.Foundation.NSDirectoryEnumerator import platform.Foundation.NSFileManager @@ -14,16 +12,18 @@ import platform.Foundation.NSURL import platform.Foundation.pathExtension fun NSBundle.Companion.loadableBundle(identifier: String): NSBundle { - // we should use search by our class because dynamic framework with resources can be placed in - // external directory, not inside app directory (NSBundle.main). for example in case of - // SwiftUI preview - app directory empty, but dynamic framework with resources will be in - // different directory (DerivedData) - // more details inside resources-build-logic/src/main/kotlin/apple-bundle-searcher-convention.gradle.kts - @OptIn(ExperimentalForeignApi::class) - val rootBundle: NSBundle = requireNotNull(ResourcesBundleAnchor.getResourcesBundle()) { - "root NSBundle can't be found" - } - val bundlePath: String = rootBundle.bundlePath + // at first we try to find required bundle inside Bundle.main, because it's faster way + // https://github.com/icerockdev/moko-resources/issues/708 + // but in some cases (for example in SwiftUI Previews) dynamic framework with bundles can be located + // in different location, not inside Bundle.main. So in this case we run less performant way - bundleWithIdentifier + // https://github.com/icerockdev/moko-resources/issues/747 + return findBundleInMain(identifier) + ?: NSBundle.bundleWithIdentifier(identifier) + ?: throw IllegalArgumentException("bundle with identifier $identifier not found") +} + +private fun findBundleInMain(identifier: String): NSBundle? { + val bundlePath: String = NSBundle.mainBundle.bundlePath val enumerator: NSDirectoryEnumerator = requireNotNull( NSFileManager.defaultManager.enumeratorAtPath(bundlePath) @@ -46,7 +46,7 @@ fun NSBundle.Companion.loadableBundle(identifier: String): NSBundle { } } - throw IllegalArgumentException("bundle with identifier $identifier not found") + return null } var isBundleSearchLogEnabled = false diff --git a/resources/src/appleMain/objective-c/MRResourcesBundle.h b/resources/src/appleMain/objective-c/MRResourcesBundle.h deleted file mode 100644 index b77ef65e..00000000 --- a/resources/src/appleMain/objective-c/MRResourcesBundle.h +++ /dev/null @@ -1,9 +0,0 @@ -#import -#import -#import - -@interface ResourcesBundleAnchor : NSObject - -+ (NSBundle*) getResourcesBundle; - -@end diff --git a/resources/src/appleMain/objective-c/MRResourcesBundle.m b/resources/src/appleMain/objective-c/MRResourcesBundle.m deleted file mode 100644 index 1d1e819e..00000000 --- a/resources/src/appleMain/objective-c/MRResourcesBundle.m +++ /dev/null @@ -1,13 +0,0 @@ -// clang -target arm64-apple-ios -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -c MRResourcesBundle.m -o source.o -// ar rcs libMRResourcesBundle.a source.o -// lipo -info libMRResourcesBundle.a - -#import "MRResourcesBundle.h" - -@implementation ResourcesBundleAnchor - -+ (NSBundle*) getResourcesBundle { - return [NSBundle bundleForClass:[ResourcesBundleAnchor class]]; -} - -@end