From 21a69b0e5b3e6ee05426c8422ffc73debce91bca Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Tue, 26 Feb 2019 17:01:59 +0300 Subject: [PATCH] CocoaPods: Basic support This patch adds a separate Gradle plugin allowing a user to import Kotlin/Native modules in Cocoapods projects and use Cocoapods dependencies in Kotlin code via cinterop. The plugin when applied does the following things: 1. Add a framework binary for each supported (iOS or macOS) platform in the project. 2. Add a Cocoapods extension allowing one to configure Cocoapods dependencies of the project. 3. Create cinterop tasks for each dependency from the previous point. The tasks are added for main compilations of each supported platform. 4. Add a task to generate a podspec-file which includes the framework from the point 1, script to build it and dependencies from the point 2. So the Cocoapods import procedure is the following: 1. A user runs `./gradlew generatePodspec`. The plugin creates a podspec-file with all dependencies. 2. The user adds a dependency on this podspec in his Podfile and runs `pod install`. Cocoapods downloads dependencies and configures user's XCode project. 3. The user runs XCode build. XCode builds dependencies and then runs Gradle build. Gradle performs interop processing and builds the Kotlin framework. Compiler options and header search paths are passed in the Gradle from XCode environment variables. After that the final application is built by XCode. Issue #KT-30269 Fixed --- .../kotlin-gradle-plugin/build.gradle.kts | 4 +- .../plugin/cocoapods/CocoapodsExtension.kt | 81 +++++++ .../plugin/cocoapods/KotlinCocoapodsPlugin.kt | 223 ++++++++++++++++++ .../kotlin/gradle/tasks/CocoapodsTasks.kt | 155 ++++++++++++ .../kotlin/gradle/utils/stringUtils.kt | 9 +- .../kotlin-cocoapods.properties | 1 + .../org.jetbrains.kotlin.cocoapods.properties | 1 + .../cocoapods/dummy.framework/Headers/dummy.h | 5 + .../cocoapods/dummy.framework/Info.plist | Bin 0 -> 708 bytes .../dummy.framework/Modules/module.modulemap | 6 + .../resources/cocoapods/dummy.framework/dummy | Bin 0 -> 23120 bytes 11 files changed, 483 insertions(+), 2 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/CocoapodsExtension.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/KotlinCocoapodsPlugin.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/CocoapodsTasks.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-cocoapods.properties create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.cocoapods.properties create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Headers/dummy.h create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Info.plist create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Modules/module.modulemap create mode 100755 libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/dummy diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index 77859c2dfef..771c10f52bb 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -90,7 +90,9 @@ tasks { for ((name, value) in propertiesToExpand) { inputs.property(name, value) } - expand("projectVersion" to project.version) + filesMatching("project.properties") { + expand("projectVersion" to project.version) + } } named("jar") { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/CocoapodsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/CocoapodsExtension.kt new file mode 100644 index 00000000000..d4bc0ad96f5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/CocoapodsExtension.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.cocoapods + +import org.gradle.api.Named +import org.gradle.api.NamedDomainObjectSet +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional + +open class CocoapodsExtension(private val project: Project) { + @get:Input + val version: String + get() = project.version.toString() + + /** + * Configure authors of the pod built from this project. + */ + @Optional + @Input + var authors: String? = null + + /** + * Configure license of the pod built from this project. + */ + @Optional + @Input + var license: String? = null + + /** + * Configure description of the pod built from this project. + */ + @Optional + @Input + var summary: String? = null + + /** + * Configure homepage of the pod built from this project. + */ + @Optional + @Input + var homepage: String? = null + + private val _pods = project.container(CocoapodsDependency::class.java) + + // For some reason Gradle doesn't consume the @Nested annotation on NamedDomainObjectContainer. + @get:Nested + protected val podsAsTaskInput: List + get() = _pods.toList() + + /** + * Returns a list of pod dependencies. + */ + // Already taken into account as a task input in the [podsAsTaskInput] property. + @get:Internal + val pods: NamedDomainObjectSet + get() = _pods + + /** + * Add a Cocoapods dependency to the pod built from this project. + */ + @JvmOverloads + fun pod(name: String, version: String? = null, moduleName: String = name) { + check(_pods.findByName(name) == null) { "Project already has a CocoaPods dependency with name $name" } + _pods.add(CocoapodsDependency(name, version, moduleName)) + } + + data class CocoapodsDependency( + private val name: String, + @get:Optional @get:Input val version: String?, + @get:Input val moduleName: String + ) : Named { + @Input + override fun getName(): String = name + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/KotlinCocoapodsPlugin.kt new file mode 100644 index 00000000000..3d651b0b0ab --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/cocoapods/KotlinCocoapodsPlugin.kt @@ -0,0 +1,223 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.cocoapods + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.Sync +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.addExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.whenEvaluated +import org.jetbrains.kotlin.gradle.tasks.DefFileTask +import org.jetbrains.kotlin.gradle.tasks.DummyFrameworkTask +import org.jetbrains.kotlin.gradle.tasks.PodspecTask +import org.jetbrains.kotlin.gradle.utils.asValidTaskName +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.konan.target.HostManager +import java.io.File + +internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs + get() = CocoapodsBuildDirs(this) + +internal class CocoapodsBuildDirs(val project: Project) { + val root: File + get() = project.buildDir.resolve("cocoapods") + + val framework: File + get() = root.resolve("framework") + + val defs: File + get() = root.resolve("defs") +} + +private enum class State { + CONSUME_ESCAPED, + CONSUME, + SKIP; +} + +/** + * Splits a string using a whitespace characters as delimiters. + * Ignores whitespaces in quotes and drops quotes, e.g. a string + * `foo "bar baz" qux="quux"` will be split into ["foo", "bar baz", "qux=quux"]. + */ +private fun String.splitQuotedArgs(): List { + if (isEmpty()) { + return emptyList() + } + + var state: State = State.SKIP + val result = mutableListOf() + val token = StringBuilder(length) + + forEachIndexed { index, char -> + when (state) { + State.CONSUME_ESCAPED -> { + when (char) { + '"' -> state = State.CONSUME // Skip `"` + else -> token.append(char) + } + } + State.CONSUME -> { + when { + char == '"' -> state = State.CONSUME_ESCAPED // Skip `"` + char.isWhitespace() -> { + state = State.SKIP + result.add(token.toString()) + token.setLength(0) + } + else -> token.append(char) + } + } + State.SKIP -> { + when { + char == '"' -> state = State.CONSUME_ESCAPED // Skip `"` + !char.isWhitespace() -> { + state = State.CONSUME + token.append(char) + } + } + } + } + } + if (token.isNotEmpty()) { + result.add(token.toString()) + } + return result +} + +open class KotlinCocoapodsPlugin: Plugin { + + private fun KotlinMultiplatformExtension.supportedTargets() = targets + .withType(KotlinNativeTarget::class.java) + .matching { it.konanTarget.family == Family.IOS || it.konanTarget.family == Family.OSX } + + private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension) { + kotlinExtension.supportedTargets().all { target -> + target.binaries.framework { + // TODO: Add in the framework DSL. + this.freeCompilerArgs.add("-Xstatic-framework") + } + } + } + + private fun createSyncTask( + project: Project, + kotlinExtension: KotlinMultiplatformExtension + ) = project.whenEvaluated { + val requestedTargetName = project.findProperty(TARGET_PROPERTY)?.toString() ?: return@whenEvaluated + val requestedBuildType = project.findProperty(CONFIGURATION_PROPERTY)?.toString()?.toUpperCase() ?: return@whenEvaluated + + val requestedTarget = HostManager().targetByName(requestedTargetName) + + val targets = kotlinExtension.supportedTargets().matching { + it.konanTarget == requestedTarget + } + + check(targets.isNotEmpty()) { "The project doesn't contain a target for the requested platform: $requestedTargetName" } + check(targets.size == 1) { "The project has more than one targets for the requested platform: $requestedTargetName" } + + val framework = targets.single().binaries.getFramework(requestedBuildType) + project.tasks.create("syncFramework", Sync::class.java) { + it.group = TASK_GROUP + it.description = "Copies a framework for given platform and build type into the cocoapods build directory" + + it.dependsOn(framework.linkTask) + it.from(framework.linkTask.destinationDir) + it.destinationDir = cocoapodsBuildDirs.framework + } + } + + private fun createPodspecGenerationTask( + project: Project, + cocoapodsExtension: CocoapodsExtension + ) { + val dummyFrameworkTask = project.tasks.create("generateDummyFramework", DummyFrameworkTask::class.java) + + project.tasks.create("generatePodspec", PodspecTask::class.java) { + it.group = TASK_GROUP + it.description = "Generates a podspec file for Cocoapods import" + it.settings = cocoapodsExtension + it.dependsOn(dummyFrameworkTask) + } + } + + private fun createInterops( + project: Project, + kotlinExtension: KotlinMultiplatformExtension, + cocoapodsExtension: CocoapodsExtension + ) { + cocoapodsExtension.pods.all { pod -> + kotlinExtension.supportedTargets().all { target -> + target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.create(pod.name) { interop -> + + val defTask = project.tasks.create( + lowerCamelCaseName("generateDef", pod.name, target.name).asValidTaskName(), + DefFileTask::class.java + ) { + it.pod = pod + it.description = "Generates a def file for Cocoapods dependency ${pod.name} for target ${target.name}" + // This task is an implementation detail so we don't add it in any group + // to avoid showing it in the `tasks` output. + } + + project.tasks.getByPath(interop.interopProcessingTaskName).dependsOn(defTask) + interop.defFile = defTask.outputFile + interop.packageName = "cocoapods.${pod.moduleName}" + + project.findProperty(CFLAGS_PROPERTY)?.toString()?.let { args -> + // XCode quotes around paths with spaces. + // Here and below we need to split such paths taking this into account. + interop.compilerOpts.addAll(args.splitQuotedArgs()) + } + project.findProperty(HEADER_PATHS_PROPERTY)?.toString()?.let { args-> + interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-I$it" }) + } + project.findProperty(FRAMEWORK_PATHS_PROPERTY)?.toString()?.let { args -> + interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-F$it" }) + } + } + } + } + } + + override fun apply(project: Project): Unit = with(project) { + pluginManager.withPlugin("kotlin-multiplatform") { + val kotlinExtension = project.kotlinExtension as? KotlinMultiplatformExtension + if (kotlinExtension == null) { + logger.info("Cannot apply cocoapods plugin: Cannot cast ${project.kotlinExtension} to KotlinMultiplatformExtension") + return@withPlugin + } + + val cocoapodsExtension = CocoapodsExtension(this) + + kotlinExtension.addExtension(EXTENSION_NAME, cocoapodsExtension) + createDefaultFrameworks(kotlinExtension) + createSyncTask(project, kotlinExtension) + createPodspecGenerationTask(project, cocoapodsExtension) + createInterops(project, kotlinExtension, cocoapodsExtension) + } + } + + companion object { + const val EXTENSION_NAME = "cocoapods" + const val TASK_GROUP = "cocoapods" + + // We don't move these properties in PropertiesProvider because + // they are not intended to be overridden in local.properties. + const val TARGET_PROPERTY = "kotlin.native.cocoapods.target" + const val CONFIGURATION_PROPERTY = "kotlin.native.cocoapods.configuration" + + const val CFLAGS_PROPERTY = "kotlin.native.cocoapods.cflags" + const val HEADER_PATHS_PROPERTY = "kotlin.native.cocoapods.paths.headers" + const val FRAMEWORK_PATHS_PROPERTY = "kotlin.native.cocoapods.paths.frameworks" + + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/CocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/CocoapodsTasks.kt new file mode 100644 index 00000000000..7ed9e4d4ce5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/CocoapodsTasks.kt @@ -0,0 +1,155 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin +import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs +import java.io.File + +/** + * The task generates a podspec file which allows a user to + * integrate a Kotlin/Native framework into a Cocoapods project. + */ +open class PodspecTask: DefaultTask() { + + @OutputFile + val outputFile: File = project.projectDir.resolve("${project.name}.podspec") + + @Nested + lateinit var settings: CocoapodsExtension + + // TODO: Handle Framework name customization - rename the framework during sync process. + @TaskAction + fun generate() { + val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile).path + val dependencies = settings.pods.map { pod -> + val versionSuffix = if (pod.version != null) ", '${pod.version}'" else "" + "| spec.dependency '${pod.name}'$versionSuffix" + }.joinToString(separator = "\n") + val specName = project.name.replace('-', '_') + + outputFile.writeText(""" + |Pod::Spec.new do |spec| + | spec.name = '$specName' + | spec.version = '${settings.version}' + | spec.homepage = '${settings.homepage.orEmpty()}' + | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } + | spec.authors = '${settings.authors.orEmpty()}' + | spec.license = '${settings.license.orEmpty()}' + | spec.summary = '${settings.summary.orEmpty()}' + | + | spec.static_framework = true + | spec.vendored_frameworks = "$frameworkDir/#{spec.name}.framework" + | spec.libraries = "c++" + | spec.module_name = "#{spec.name}_umbrella" + | + $dependencies + | + | spec.pod_target_xcconfig = { + | 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64', + | 'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm64', + | 'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64' + | } + | + | spec.script_phases = [ + | { + | :name => 'Build $specName', + | :execution_position => :before_compile, + | :shell_path => '/bin/sh', + | :script => <<-SCRIPT + | set -ev + | REPO_ROOT=`realpath "${'$'}PODS_TARGET_SRCROOT"` + | ${'$'}REPO_ROOT/gradlew -p "${'$'}REPO_ROOT" syncFramework \ + | -P${KotlinCocoapodsPlugin.TARGET_PROPERTY}=${'$'}KOTLIN_TARGET \ + | -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}=${'$'}CONFIGURATION \ + | -P${KotlinCocoapodsPlugin.CFLAGS_PROPERTY}="${'$'}OTHER_CFLAGS" \ + | -P${KotlinCocoapodsPlugin.HEADER_PATHS_PROPERTY}="${'$'}HEADER_SEARCH_PATHS" \ + | -P${KotlinCocoapodsPlugin.FRAMEWORK_PATHS_PROPERTY}="${'$'}FRAMEWORK_SEARCH_PATHS" + | SCRIPT + | } + | ] + |end + """.trimMargin()) + } +} + +/** + * Creates a dummy framework in the target directory. + * + * We represent a Kotlin/Native module to Cocoapods as a vendored framework. + * Cocoapods needs access to such frameworks during installation process to obtain + * their type (static or dynamic) and configure the XCode project accordingly. + * But we cannot build the real framework before installation because it may + * depend on Cocoapods libraries which are not downloaded and built at this stage. + * So we create a dummy static framework to allow Cocoapods install our pod correctly + * and then replace it with the real one during a real build process. + */ +open class DummyFrameworkTask: DefaultTask() { + @OutputDirectory + val destinationDir = project.cocoapodsBuildDirs.framework + + @get:Input + val frameworkName + get() = project.name.replace('-', '_') + + private val frameworkDir: File + get() = destinationDir.resolve("$frameworkName.framework") + + private fun copyResource(from: String, to: File) { + to.parentFile.mkdirs() + to.outputStream().use { file -> + javaClass.getResourceAsStream(from).use { resource -> + resource.copyTo(file) + } + } + } + + private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) = + copyResource( + "/cocoapods/dummy.framework/$relativeFrom", + frameworkDir.resolve(relativeTo) + ) + + @TaskAction + fun create() { + // Reset the destination directory + with(destinationDir) { + deleteRecursively() + mkdirs() + } + + // Copy files for the dummy framework. + copyFrameworkFile("Info.plist") + copyFrameworkFile("dummy", frameworkName) + copyFrameworkFile("Modules/module.modulemap") + copyFrameworkFile("Headers/dummy.h") + } +} + +/** + * Generates a def-file for the given Cocoapods dependency. + */ +open class DefFileTask : DefaultTask() { + + @Nested + lateinit var pod: CocoapodsExtension.CocoapodsDependency + + @get:OutputFile + val outputFile: File + get() = project.cocoapodsBuildDirs.defs.resolve("${pod.name}.def") + + @TaskAction + fun generate() { + outputFile.parentFile.mkdirs() + outputFile.writeText(""" + language = Objective-C + modules = ${pod.moduleName} + """.trimIndent()) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt index 6da109d68af..d44372163ed 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt @@ -19,4 +19,11 @@ internal fun dashSeparatedName(nameParts: Iterable) = dashSeparatedName internal fun dashSeparatedName(vararg nameParts: String?): String { val nonEmptyParts = nameParts.mapNotNull { it?.takeIf(String::isNotEmpty) } return nonEmptyParts.joinToString(separator = "-") -} \ No newline at end of file +} + +private val invalidTaskNameCharacters = "[/\\\\:<>\"?*|]".toRegex() + +/** + * Replaces characters which are not allowed in Gradle task names (/, \, :, <, >, ", ?, *, |) with '_' + */ +internal fun String.asValidTaskName() = replace(invalidTaskNameCharacters, "_") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-cocoapods.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-cocoapods.properties new file mode 100644 index 00000000000..ca158c10e62 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin-cocoapods.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.cocoapods.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.cocoapods.properties new file mode 100644 index 00000000000..ca158c10e62 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.cocoapods.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Headers/dummy.h b/libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Headers/dummy.h new file mode 100644 index 00000000000..a91320276ef --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Headers/dummy.h @@ -0,0 +1,5 @@ +//! Project version number for dummy. +FOUNDATION_EXPORT double dummyVersionNumber; + +//! Project version string for dummy. +FOUNDATION_EXPORT const unsigned char dummyVersionString[]; diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Info.plist b/libraries/tools/kotlin-gradle-plugin/src/main/resources/cocoapods/dummy.framework/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..8fcb3c949a9e4b2fabe0d8066d5b9e0896644d9f GIT binary patch literal 708 zcmZ8dOK;Oa5Z+l{1zNIg3Z;dX5-3m(mdzsqaZ6035Ty;VO?gzYTziwO*tIP`Dgu9i z8$W;(9JzqR35hEQE*vWE+z>bZ0vp#>D)unn%zWQBn%SL@vp7-JUt}Kep~FXx9-BIT z;^g$qsS=r;E6<-kvv78CVd>oYl?$sEE0->>tyZqsq+ClG_u7s-WIo+AvdSil^+qlA zJx+DHOL-6q>L*<~V1aLwxq`d@g1Twq>~MOZ+4Q88^%;$9vRo*eem~Hen@GPCjULe` zmf3c}Za6|tC>LeNai2Q_YK}tLT`Ic9Fo=@eX(SQz2M5VC3?)sy4tJ7%5Q*3(rNWt; zp6X^j5Fz8^%}TmiNVAK3lX`!f*-2yKH}qDfexjQ)9SgT~^RXLvbUVlV`$(JlOr&Dd z$jv>gDSwi=wBZQGN0xS{e*0FJ`j(dpF?yCoC5uK?QO!nst7T}4`fgdS$~wcqr-qy- zj|-xQnoTNhAQUGIIaOqq-4rXF?+V4`jz3T|TeUQ`R+Z8c3&-}ju$$TqMYFVO?XD!t z|B++P)KpDK^No*3EbV*XlgLItTxk)D3oeA@? z-7O*!Y$f1B{Y0S!!L(M3MG>I|LxUeQg3y|mB0?4HL;b=Qe2AeG&zYGdyDLQ>RsI*w z-215MeZ zDJsdzqF*%4lR0n2t#rO~!NM!QmugWVCRcvt{iJNmU77HD^KFa<@q8796O^<$&Mq=d z+u7asL}y3$QmjkyC87b5PV|JD<5Z%bxLQNjLUspP*w0@@v^f*HH>w(bCXhBvc7Ji_1TZP@)|9qZ=iaP>dUlLHMJ9DT=+cM*td$TaGxaqQCUY` zyw3BfXJ)jITCaV4?*rLyKdlp+sV_#x@e8FUFrJ_9k*Il7*+3O-X}OTcG8f)Y<=jO& z4n?TG>!@in$u^L8J(-Dbj~jVX;n2H@OCvRTe4EV4l<(=uwA2Eck1ug_MUh|H)(z?W z#fN9SpN#jlkv`0%Dbmc5%ROY=oLwu#!5!R#0s0|ZH?=j z68*b&^gq_y8ay^*F_Jx4;&qP-XOMRC#N($HMbPijfiqGzH|ce@n4IwrlOqnnni z?lh_4fzr^qK-dcgLb zlyp@QoSBxTeaBP(i2!yb$7ITLn=RYP=xH_AvQ^uEq2HQvlsRybX$mr%Mjp6N3wvZ3&3Q-nhfcR>CPBuiwV@R)^jgAJbtJ5 z_D#R^(>JH)sy0{Yw@v%U-uZFe@5F%rE1X6Vix;Z7&FK#m5C8!X009sH0T2KI5C8!X z009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH q0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X0D=Drfj