migrated podspec tests from CocoaPodsIT to new test DSL (gradle test kit + JUnit 5)

#KT-51553 In Progress
This commit is contained in:
Dmitrii Krasnov
2023-05-25 10:58:41 +02:00
committed by Space Team
parent a0819e4163
commit f8a540f4cf
9 changed files with 285 additions and 157 deletions
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SETUP_BUILD_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME
import org.jetbrains.kotlin.gradle.testbase.TestVersions
import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.gradle.testbase.normalizeCocoapadsFrameworkName
import org.jetbrains.kotlin.gradle.util.createTempDir
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.util.runProcess
@@ -33,9 +33,6 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
private val String.validFrameworkName: String
get() = replace('-', '_')
class CocoaPodsIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
@@ -112,20 +109,6 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
@Test
fun testPodspecSingle() = doTestPodspec(
cocoapodsSingleKtPod,
mapOf("kotlin-library" to null),
mapOf("kotlin-library" to kotlinLibraryPodspecContent())
)
@Test
fun testPodspecCustomFrameworkNameSingle() = doTestPodspec(
cocoapodsSingleKtPod,
mapOf("kotlin-library" to "MultiplatformLibrary"),
mapOf("kotlin-library" to kotlinLibraryPodspecContent("MultiplatformLibrary"))
)
@Test
fun testXcodeUseFrameworksSingle() = doTestXcode(
cocoapodsSingleKtPod,
@@ -211,23 +194,6 @@ class CocoaPodsIT : BaseGradleIT() {
project.testSynthetic(":second-library:podImport")
}
@Test
fun testPodspecMultiple() = doTestPodspec(
cocoapodsMultipleKtPods,
mapOf("kotlin-library" to null, "second-library" to null),
mapOf("kotlin-library" to kotlinLibraryPodspecContent(), "second-library" to secondLibraryPodspecContent("second_library")),
)
@Test
fun testPodspecCustomFrameworkNameMultiple() = doTestPodspec(
cocoapodsMultipleKtPods,
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary"),
mapOf(
"kotlin-library" to kotlinLibraryPodspecContent("FirstMultiplatformLibrary"),
"second-library" to secondLibraryPodspecContent("SecondMultiplatformLibrary")
)
)
@Test
fun testXcodeUseFrameworksMultiple() = doTestXcode(
cocoapodsMultipleKtPods,
@@ -1554,38 +1520,7 @@ class CocoaPodsIT : BaseGradleIT() {
if (iosAppLocation != null) {
val iosAppDir = projectDir.resolve(iosAppLocation)
iosAppDir.resolve("ios-app/ViewController.swift").modify {
it.replace("import ${subproject.validFrameworkName}", "import $frameworkName")
}
}
}
private fun doTestPodspec(
projectName: String,
subprojectsToFrameworkNamesMap: Map<String, String?>,
subprojectsToPodspecContentMap: Map<String, String?>
) {
val gradleProject = transformProjectWithPluginsDsl(projectName, gradleVersion)
for ((subproject, frameworkName) in subprojectsToFrameworkNamesMap) {
frameworkName?.let {
gradleProject.useCustomFrameworkName(subproject, it)
}
// Check that we can generate the wrapper along with the podspec if the corresponding property specified
gradleProject.build(":$subproject:podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
assertSuccessful()
assertTasksExecuted(":$subproject:podspec")
// Check that the podspec file is correctly generated.
val podspecFileName = "$subproject/${subproject.validFrameworkName}.podspec"
assertFileExists(podspecFileName)
val actualPodspecContentWithoutBlankLines = fileInWorkingDir(podspecFileName).readText()
.lineSequence()
.filter { it.isNotBlank() }
.joinToString("\n")
assertEquals(subprojectsToPodspecContentMap[subproject], actualPodspecContentWithoutBlankLines)
it.replace("import ${subproject.normalizeCocoapadsFrameworkName}", "import $frameworkName")
}
}
}
@@ -1730,83 +1665,6 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
private fun kotlinLibraryPodspecContent(frameworkName: String? = null) = """
Pod::Spec.new do |spec|
spec.name = 'kotlin_library'
spec.version = '1.0'
spec.homepage = 'https://github.com/JetBrains/kotlin'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'CocoaPods test library'
spec.vendored_frameworks = 'build/cocoapods/framework/${frameworkName ?: "kotlin_library"}.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '11.0'
spec.dependency 'pod_dependency', '1.0'
spec.dependency 'subspec_dependency/Core', '1.0'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':kotlin-library',
'PRODUCT_MODULE_NAME' => '${frameworkName ?: "kotlin_library"}',
}
spec.script_phases = [
{
:name => 'Build kotlin_library',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
"${'$'}REPO_ROOT/../gradlew" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=${'$'}PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="${'$'}ARCHS" \
-Pkotlin.native.cocoapods.configuration="${'$'}CONFIGURATION"
SCRIPT
}
]
end
""".trimIndent()
private fun secondLibraryPodspecContent(frameworkName: String? = null) = """
Pod::Spec.new do |spec|
spec.name = 'second_library'
spec.version = '1.0'
spec.homepage = 'https://github.com/JetBrains/kotlin'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'CocoaPods test library'
spec.vendored_frameworks = 'build/cocoapods/framework/${frameworkName ?: "second_library"}.framework'
spec.libraries = 'c++'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':second-library',
'PRODUCT_MODULE_NAME' => '${frameworkName ?: "kotlin_library"}',
}
spec.script_phases = [
{
:name => 'Build second_library',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
"${'$'}REPO_ROOT/../gradlew" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=${'$'}PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="${'$'}ARCHS" \
-Pkotlin.native.cocoapods.configuration="${'$'}CONFIGURATION"
SCRIPT
}
]
end
""".trimIndent()
private val publishPodspecContent = """
Pod::Spec.new do |spec|
spec.name = 'cocoapods'
@@ -0,0 +1,175 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.native
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS
import kotlin.io.path.readText
import kotlin.test.assertEquals
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("K/N tests cocoapods")
@NativeGradlePluginTests
class CocoaPodsPodspecIT : KGPBaseTest() {
private val cocoapodsSingleKtPod = "native-cocoapods-single"
private val cocoapodsMultipleKtPods = "native-cocoapods-multiple"
@DisplayName("Build project with a single podspec")
@GradleTest
fun testPodspecSingle(gradleVersion: GradleVersion) = doTestPodspec(
cocoapodsSingleKtPod,
gradleVersion,
mapOf("kotlin-library" to null),
mapOf("kotlin-library" to kotlinLibraryPodspecContent())
)
@DisplayName("Build project with a single podspec with custom framework name")
@GradleTest
fun testPodspecCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestPodspec(
cocoapodsSingleKtPod,
gradleVersion,
mapOf("kotlin-library" to "MultiplatformLibrary"),
mapOf("kotlin-library" to kotlinLibraryPodspecContent("MultiplatformLibrary"))
)
@DisplayName("Build project with two podspecs")
@GradleTest
fun testPodspecMultiple(gradleVersion: GradleVersion) = doTestPodspec(
cocoapodsMultipleKtPods,
gradleVersion,
mapOf("kotlin-library" to null, "second-library" to null),
mapOf("kotlin-library" to kotlinLibraryPodspecContent(), "second-library" to secondLibraryPodspecContent("second_library")),
)
@DisplayName("Build project with two podspecs with custom framework name")
@GradleTest
fun testPodspecCustomFrameworkNameMultiple(gradleVersion: GradleVersion) = doTestPodspec(
cocoapodsMultipleKtPods,
gradleVersion,
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary"),
mapOf(
"kotlin-library" to kotlinLibraryPodspecContent("FirstMultiplatformLibrary"),
"second-library" to secondLibraryPodspecContent("SecondMultiplatformLibrary")
)
)
private fun doTestPodspec(
projectName: String,
gradleVersion: GradleVersion,
subprojectsToFrameworkNamesMap: Map<String, String?>,
subprojectsToPodspecContentMap: Map<String, String?>,
) {
nativeProject(projectName, gradleVersion) {
for ((subproject, frameworkName) in subprojectsToFrameworkNamesMap) {
frameworkName?.let {
useCustomCocoapodsFrameworkName(subproject, it)
}
// Check that we can generate the wrapper along with the podspec if the corresponding property specified
build(
":$subproject:podspec",
buildOptions = defaultBuildOptions.copy(
nativeOptions = defaultBuildOptions.nativeOptions.copy(
cocoapodsGenerateWrapper = true
)
)
) {
assertTasksExecuted(":$subproject:podspec")
// Check that the podspec file is correctly generated.
val podspecFileName = "$subproject/${subproject.normalizeCocoapadsFrameworkName}.podspec"
assertFileInProjectExists(podspecFileName)
val actualPodspecContentWithoutBlankLines = projectPath.resolve(podspecFileName).readText()
.lineSequence()
.filter { it.isNotBlank() }
.joinToString("\n")
assertEquals(subprojectsToPodspecContentMap[subproject], actualPodspecContentWithoutBlankLines)
}
}
}
}
private fun kotlinLibraryPodspecContent(frameworkName: String? = null) = """
Pod::Spec.new do |spec|
spec.name = 'kotlin_library'
spec.version = '1.0'
spec.homepage = 'https://github.com/JetBrains/kotlin'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'CocoaPods test library'
spec.vendored_frameworks = 'build/cocoapods/framework/${frameworkName ?: "kotlin_library"}.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '11.0'
spec.dependency 'pod_dependency', '1.0'
spec.dependency 'subspec_dependency/Core', '1.0'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':kotlin-library',
'PRODUCT_MODULE_NAME' => '${frameworkName ?: "kotlin_library"}',
}
spec.script_phases = [
{
:name => 'Build kotlin_library',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
"${'$'}REPO_ROOT/../gradlew" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=${'$'}PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="${'$'}ARCHS" \
-Pkotlin.native.cocoapods.configuration="${'$'}CONFIGURATION"
SCRIPT
}
]
end
""".trimIndent()
private fun secondLibraryPodspecContent(frameworkName: String? = null) = """
Pod::Spec.new do |spec|
spec.name = 'second_library'
spec.version = '1.0'
spec.homepage = 'https://github.com/JetBrains/kotlin'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'CocoaPods test library'
spec.vendored_frameworks = 'build/cocoapods/framework/${frameworkName ?: "second_library"}.framework'
spec.libraries = 'c++'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':second-library',
'PRODUCT_MODULE_NAME' => '${frameworkName ?: "kotlin_library"}',
}
spec.script_phases = [
{
:name => 'Build second_library',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
"${'$'}REPO_ROOT/../gradlew" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=${'$'}PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="${'$'}ARCHS" \
-Pkotlin.native.cocoapods.configuration="${'$'}CONFIGURATION"
SCRIPT
}
]
end
""".trimIndent()
}
@@ -334,7 +334,8 @@ internal class NativeIrLinkerIssuesIT : KGPBaseTest() {
nativeProject(
directoryPrefix + "/" + projectName,
gradleVersion = gradleVersion,
buildOptions = defaultBuildOptions.copy(nativeCacheKind = nativeCacheKind),
buildOptions = defaultBuildOptions.copy(
nativeOptions = defaultBuildOptions.nativeOptions.copy(cacheKind = nativeCacheKind)),
localRepoDir = localRepo
) {
block()
@@ -44,7 +44,7 @@ data class BuildOptions(
val keepIncrementalCompilationCachesInMemory: Boolean? = null,
val useDaemonFallbackStrategy: Boolean = false,
val verboseDiagnostics: Boolean = true,
val nativeCacheKind: NativeCacheKind = NativeCacheKind.NONE
val nativeOptions: NativeOptions = NativeOptions(),
) {
val safeAndroidVersion: String
get() = androidVersion ?: error("AGP version is expected to be set")
@@ -62,7 +62,18 @@ data class BuildOptions(
val incrementalJs: Boolean? = null,
val incrementalJsKlib: Boolean? = null,
val incrementalJsIr: Boolean? = null,
val compileNoWarn: Boolean = true
val compileNoWarn: Boolean = true,
)
data class NativeOptions(
val cacheKind: NativeCacheKind = NativeCacheKind.NONE,
val cocoapodsGenerateWrapper: Boolean? = null,
val distributionType: String? = null,
val distributionDownloadFromMaven: Boolean? = null,
val platformLibrariesMode: String? = null,
val reinstall: Boolean? = null,
val restrictedDistribution: Boolean? = null,
val version: String? = null,
)
fun toArguments(
@@ -112,6 +123,8 @@ data class BuildOptions(
arguments.add(if (buildCacheEnabled) "--build-cache" else "--no-build-cache")
addNativeOptionsToArguments(arguments)
if (kaptOptions != null) {
arguments.add("-Pkapt.verbose=${kaptOptions.verbose}")
arguments.add("-Pkapt.incremental.apt=${kaptOptions.incrementalKapt}")
@@ -172,12 +185,41 @@ data class BuildOptions(
arguments.add("-Pkotlin.internal.verboseDiagnostics=$verboseDiagnostics")
}
arguments.add("-Pkotlin.native.cacheKind=${nativeCacheKind.name.lowercase()}")
arguments.addAll(freeArgs)
return arguments.toList()
}
private fun addNativeOptionsToArguments(
arguments: MutableList<String>,
) {
arguments.add("-Pkotlin.native.cacheKind=${nativeOptions.cacheKind.name.lowercase()}")
nativeOptions.cocoapodsGenerateWrapper?.let {
arguments.add("-Pkotlin.native.cocoapods.generate.wrapper=${it}")
}
nativeOptions.distributionDownloadFromMaven?.let {
arguments.add("-Pkotlin.native.distribution.downloadFromMaven=${it}")
}
nativeOptions.distributionType?.let {
arguments.add("-Pkotlin.native.distribution.type=${it}")
}
nativeOptions.platformLibrariesMode?.let {
arguments.add("-Pkotlin.native.platform.libraries.mode=${it}")
}
nativeOptions.reinstall?.let {
arguments.add("-Pkotlin.native.reinstall=${it}")
}
nativeOptions.restrictedDistribution?.let {
arguments.add("-Pkotlin.native.restrictedDistribution=${it}")
}
nativeOptions.version?.let {
arguments.add("-Pkotlin.native.version=${it}")
}
}
}
fun BuildOptions.suppressDeprecationWarningsOn(
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.testbase
import org.jetbrains.kotlin.gradle.util.replaceText
import java.nio.file.Path
import kotlin.io.path.appendText
val String.normalizeCocoapadsFrameworkName: String
get() = replace('-', '_')
fun TestProject.useCustomCocoapodsFrameworkName(
subprojectName: String,
frameworkName: String,
iosAppLocation: String? = null,
) {
// Change the name at the Gradle side.
subProject(subprojectName)
.buildGradleKts
.addFrameworkBlock("baseName = \"$frameworkName\"")
// Change swift sources import if needed.
if (iosAppLocation != null) {
projectPath
.resolve(iosAppLocation)
.resolve("ios-app/ViewController.swift")
.replaceText(
"import ${subprojectName.normalizeCocoapadsFrameworkName}",
"import $frameworkName"
)
}
}
private fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin"))
private fun Path.addCocoapodsBlock(str: String) = addKotlinBlock(str.wrapIntoBlock("cocoapods"))
private fun Path.addFrameworkBlock(str: String) = addCocoapodsBlock(str.wrapIntoBlock("framework"))
private fun Path.appendLine(s: String) = appendText("\n$s")
@@ -23,6 +23,7 @@ internal val DEFAULT_GROOVY_SETTINGS_FILE =
id "org.jetbrains.kotlin.kapt" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.android" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.js" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.native.cocoapods" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.multiplatform" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.multiplatform.pm20" version "${'$'}kotlin_version"
id "org.jetbrains.kotlin.plugin.allopen" version "${'$'}kotlin_version"
@@ -83,6 +84,7 @@ internal val DEFAULT_KOTLIN_SETTINGS_FILE =
id("org.jetbrains.kotlin.kapt") version kotlin_version
id("org.jetbrains.kotlin.android") version kotlin_version
id("org.jetbrains.kotlin.js") version kotlin_version
id("org.jetbrains.kotlin.native.cocoapods") version kotlin_version
id("org.jetbrains.kotlin.multiplatform") version kotlin_version
id("org.jetbrains.kotlin.multiplatform.pm20") version kotlin_version
id("org.jetbrains.kotlin.plugin.allopen") version kotlin_version
@@ -46,7 +46,7 @@ fun KGPBaseTest.project(
buildJdk: File? = null,
localRepoDir: Path? = null,
environmentVariables: EnvironmentalVariables = EnvironmentalVariables(),
test: TestProject.() -> Unit = {}
test: TestProject.() -> Unit = {},
): TestProject {
val projectPath = setupProjectFromTestResources(
projectName,
@@ -108,7 +108,7 @@ fun KGPBaseTest.nativeProject(
buildJdk: File? = null,
localRepoDir: Path? = null,
environmentVariables: EnvironmentalVariables = EnvironmentalVariables(),
test: TestProject.() -> Unit = {}
test: TestProject.() -> Unit = {},
): TestProject {
val project = project(
projectName = projectName,
@@ -141,7 +141,7 @@ fun TestProject.build(
enableBuildScan: Boolean = this.enableBuildScan,
buildOptions: BuildOptions = this.buildOptions,
environmentVariables: EnvironmentalVariables = this.environmentVariables,
assertions: BuildResult.() -> Unit = {}
assertions: BuildResult.() -> Unit = {},
) {
if (enableBuildScan) agreeToBuildScanService()
@@ -178,7 +178,7 @@ fun TestProject.buildAndFail(
enableBuildScan: Boolean = this.enableBuildScan,
buildOptions: BuildOptions = this.buildOptions,
environmentVariables: EnvironmentalVariables = this.environmentVariables,
assertions: BuildResult.() -> Unit = {}
assertions: BuildResult.() -> Unit = {},
) {
if (enableBuildScan) agreeToBuildScanService()
@@ -268,6 +268,13 @@ fun TestProject.enableStatisticReports(
}
}
fun String.wrapIntoBlock(s: String): String =
"""
|$s {
| $this
|}
""".trimMargin()
open class GradleProject(
val projectName: String,
val projectPath: Path,
@@ -1,6 +1,6 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.native.cocoapods").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.multiplatform")
id("org.jetbrains.kotlin.native.cocoapods")
}
group = "com.example"
@@ -1,6 +1,6 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.native.cocoapods").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.multiplatform")
id("org.jetbrains.kotlin.native.cocoapods")
}
group = "com.example"