diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 1e0ef2038f5..8f33f996915 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2978,18 +2978,36 @@ + + + + + + + + + + + + + + + + + + @@ -3330,6 +3348,12 @@ + + + + + + @@ -3372,12 +3396,30 @@ + + + + + + + + + + + + + + + + + + @@ -3390,6 +3432,18 @@ + + + + + + + + + + + + @@ -3408,6 +3462,18 @@ + + + + + + + + + + + + @@ -3420,6 +3486,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3574,6 +3670,12 @@ + + + + + + @@ -3598,6 +3700,12 @@ + + + + + + @@ -3622,6 +3730,12 @@ + + + + + + @@ -3646,6 +3760,12 @@ + + + + + + @@ -3682,6 +3802,12 @@ + + + + + + @@ -3706,6 +3832,12 @@ + + + + + + @@ -3736,6 +3868,12 @@ + + + + + + @@ -3760,24 +3898,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3979,12 +4153,24 @@ + + + + + + + + + + + + @@ -6415,12 +6601,24 @@ + + + + + + + + + + + + @@ -6433,6 +6631,12 @@ + + + + + + @@ -6451,6 +6655,12 @@ + + + + + + @@ -6463,12 +6673,24 @@ + + + + + + + + + + + + @@ -6487,6 +6709,12 @@ + + + + + + @@ -6499,24 +6727,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -7065,6 +7317,12 @@ + + + + + + @@ -7939,6 +8197,12 @@ + + + + + + diff --git a/gradle/versions.properties b/gradle/versions.properties index 228ad4c3204..4d208409389 100644 --- a/gradle/versions.properties +++ b/gradle/versions.properties @@ -48,12 +48,12 @@ versions.kotlinx-coroutines-core=1.5.0 versions.kotlinx-metadata-jvm=0.6.0 versions.kotlinx-metadata-klib=0.0.1-dev-10 versions.kotlinx-serialization-json=1.3.3 -versions.ktor-network=1.4.0 -versions.ktor-utils=1.4.0 -versions.ktor-server-test-host=1.1.5 -versions.ktor-server-core=1.6.7 -versions.ktor-server-netty=1.6.7 -versions.ktor-client-mock=1.6.7 +versions.ktor-network=2.0.2 +versions.ktor-utils=2.0.2 +versions.ktor-server-test-host=2.0.2 +versions.ktor-server-core=2.0.2 +versions.ktor-server-netty=2.0.2 +versions.ktor-client-mock=2.0.2 versions.ktor-client-core=2.0.2 versions.ktor-client-cio=2.0.2 versions.ktor-client-websockets=2.0.2 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts index cade05db554..2fc45d4f23b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts @@ -87,6 +87,7 @@ dependencies { testImplementation(commonDependency("org.jetbrains.intellij.deps", "trove4j")) testImplementation(commonDependency("io.ktor", "ktor-server-test-host")) testImplementation(commonDependency("io.ktor", "ktor-server-core")) + testImplementation(commonDependency("io.ktor", "ktor-client-cio")) testImplementation(commonDependency("io.ktor", "ktor-server-netty")) testImplementation(commonDependency("io.ktor", "ktor-client-mock")) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt index 9af5ac93385..20c4532b156 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt @@ -8,13 +8,13 @@ package org.jetbrains.kotlin.gradle import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonParser -import io.ktor.application.* import io.ktor.http.* -import io.ktor.request.* -import io.ktor.response.* -import io.ktor.routing.* +import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* import io.ktor.util.* import io.ktor.util.collections.* import org.gradle.util.GradleVersion diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt index b5125274e96..1b6716035e5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt @@ -9,6 +9,7 @@ import org.gradle.api.JavaVersion import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.util.replaceText import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.condition.OS import kotlin.io.path.absolutePathString @@ -574,6 +575,8 @@ class AppleFrameworkIT : KGPBaseTest() { ) } + subProject("iosApp").buildGradleKts.replaceText("", "\"${TestVersions.AppleGradlePlugin.V222_0_21}\"") + build(*dependencyInsight("iosAppIosX64DebugImplementation")) { assertContainsVariant("mainDynamicDebugFrameworkIos") } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsGitIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsGitIT.kt new file mode 100644 index 00000000000..c98ba998b52 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsGitIT.kt @@ -0,0 +1,509 @@ +/* + * 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 io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.util.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking +import org.gradle.testkit.runner.BuildResult +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_BUILD_TASK_NAME +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_GEN_TASK_NAME +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME +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.* +import org.jetbrains.kotlin.gradle.util.assertProcessRunResult +import org.jetbrains.kotlin.gradle.util.capitalize +import org.jetbrains.kotlin.gradle.util.replaceText +import org.jetbrains.kotlin.gradle.util.runProcess +import org.junit.jupiter.api.Assumptions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.condition.OS +import java.nio.file.Path +import java.util.* +import kotlin.io.path.absolutePathString +import kotlin.io.path.readText +import kotlin.test.assertTrue + +@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) +@DisplayName("Git connected K/N tests with cocoapods") +@NativeGradlePluginTests +@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) +@OptIn(EnvironmentalVariablesOverride::class) +class CocoaPodsGitIT : KGPBaseTest() { + + override val defaultBuildOptions = super.defaultBuildOptions.copy( + nativeOptions = super.defaultBuildOptions.nativeOptions.copy( + cocoapodsGenerateWrapper = true + ) + ) + + private val templateProjectName = "native-cocoapods-template" + private val groovyTemplateProjectName = "native-cocoapods-template-groovy" + + private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking" + private val defaultPodName = "AFNetworking" + private val defaultTarget = "IOS" + private val defaultFamily = "ios" + private val defaultSDK = "iphonesimulator" + private val cinteropTaskName = ":cinterop" + private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget + + private val podImportTaskName = ":$POD_IMPORT_TASK_NAME" + private val podspecTaskName = ":$POD_SPEC_TASK_NAME" + private val podGenTaskName = ":$POD_GEN_TASK_NAME" + private val podSetupBuildTaskName = ":$POD_SETUP_BUILD_TASK_NAME" + private val podBuildTaskName = ":$POD_BUILD_TASK_NAME" + private val podInstallTaskName = ":$POD_INSTALL_TASK_NAME" + + private val defaultPodInstallSyntheticTaskName = ":podInstallSyntheticIos" + private val defaultPodGenTaskName = podGenFullTaskName() + private val defaultBuildTaskName = podBuildFullTaskName() + private val defaultSetupBuildTaskName = podSetupBuildFullTaskName() + + private fun podGenFullTaskName(familyName: String = defaultFamily) = + podGenTaskName + familyName.capitalize() + + private fun podSetupBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) = + podSetupBuildTaskName + podName.capitalize() + sdkName.capitalize() + + private fun podBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) = + podBuildTaskName + podName.capitalize() + sdkName.capitalize() + + private fun cinteropFullTaskName(podName: String = defaultPodName, targetName: String = defaultTarget) = + cinteropTaskName + podName.capitalize() + targetName.capitalize() + + @BeforeAll + fun setUp() { + ensureCocoapodsInstalled() + } + + @DisplayName("Downloading pod from git without specifying neither tag not commit") + @GradleTest + fun testPodDownloadGitNoTagNorCommit(gradleVersion: GradleVersion) { + doTestGit(gradleVersion) + } + + @DisplayName("Downloading pod from git with specifying tag") + @GradleTest + fun testPodDownloadGitTag(gradleVersion: GradleVersion) { + doTestGit(gradleVersion, tag = "4.0.0") + } + + @DisplayName("Downloading pod from git with specifying commit") + @GradleTest + fun testPodDownloadGitCommit(gradleVersion: GradleVersion) { + doTestGit(gradleVersion, commit = "9c07ac0a5645abb58850253eeb109ed0dca515c1") + } + + @DisplayName("Downloading pod from git with specifying branch") + @GradleTest + fun testPodDownloadGitBranch(gradleVersion: GradleVersion) { + doTestGit(gradleVersion, branch = "2974") + } + + @DisplayName("Downloading pod's subspec from git") + @GradleTest + fun testPodDownloadGitSubspec(gradleVersion: GradleVersion) { + doTestGit( + gradleVersion, + repo = "https://github.com/SDWebImage/SDWebImage.git", + pod = "SDWebImage/MapKit", + tag = "5.9.2" + ) + } + + @DisplayName("Downloading pod from git with specifying branch and commit") + @GradleTest + fun testPodDownloadGitBranchAndCommit(gradleVersion: GradleVersion) { + doTestGit( + gradleVersion, + branch = "2974", + commit = "21637dd6164c0641e414bdaf3885af6f1ef15aee" + ) + } + + + @DisplayName("Downloading pod from git (tag priority is bigger than branch priority)") + @GradleTest + fun testPodDownloadGitBranchAndTag(gradleVersion: GradleVersion) { + doTestGit( + gradleVersion, + tag = "4.0.0", + branch = "2974" + ) + } + + @DisplayName("Downloading pod from git with specifying tag for groovy build file") + @GradleTest + fun testGroovyDownloadAndImport(gradleVersion: GradleVersion) { + doTestGit( + gradleVersion, + groovyTemplateProjectName, + tag = "4.0.0", + isGradleBuildScript = true + ) + } + + @DisplayName("Checks that task cinterop is up to date during the second build") + @GradleTest + fun testCinteropUpToDate(gradleVersion: GradleVersion) { + doTestGit(gradleVersion) { + + build( + "syncFramework", + buildOptions = defaultBuildOptions.copy( + nativeOptions = defaultBuildOptions.nativeOptions.copy( + cocoapodsGenerateWrapper = true, + cocoapodsArchs = "x86_64", + cocoapodsConfiguration = "Debug", + cocoapodsPlatform = "iphonesimulator", + ) + ) + ) { + assertTasksUpToDate( + defaultCinteropTaskName + ) + } + } + + } + + @DisplayName("UTD test") + @GradleTest + fun basicUTDTest(gradleVersion: GradleVersion) { + val tasks = listOf( + podspecTaskName, + defaultPodGenTaskName, + defaultPodInstallSyntheticTaskName, + defaultSetupBuildTaskName, + defaultBuildTaskName, + defaultCinteropTaskName, + ) + doTestGit( + gradleVersion, + testImportAssertions = { assertTasksExecuted(tasks) } + ) { + testImport { + assertTasksUpToDate(tasks) + } + } + } + + @DisplayName("UTD with adding and removing pod") + @GradleTest + fun testUTDPodAdded(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + buildGradleKts.addPod(defaultPodName, produceGitBlock()) + testImport() + + val anotherPodName = "Alamofire" + val anotherPodRepo = "https://github.com/Alamofire/Alamofire" + buildGradleKts.addPod(anotherPodName, produceGitBlock(anotherPodRepo)) + testImport(listOf(defaultPodRepo, anotherPodRepo)) { + + assertTasksExecuted( + podspecTaskName, + defaultPodGenTaskName, + podSetupBuildFullTaskName(anotherPodName), + podBuildFullTaskName(anotherPodName), + cinteropFullTaskName(anotherPodName) + ) + assertTasksUpToDate( + defaultSetupBuildTaskName, + defaultBuildTaskName, + defaultCinteropTaskName + ) + + buildGradleKts.removePod(anotherPodName) + + testImport { + assertOutputDoesNotContain(podBuildFullTaskName(anotherPodName)) + assertOutputDoesNotContain(cinteropFullTaskName(anotherPodName)) + assertTasksUpToDate( + defaultBuildTaskName, + defaultSetupBuildTaskName, + defaultCinteropTaskName + ) + } + } + } + } + + @DisplayName("UTD with adding and removing target") + @GradleTest + fun testUTDTargetAdded(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + buildGradleKts.addPod(defaultPodName, produceGitBlock()) + + testImport() + + buildGradleKts.addCocoapodsBlock("osx.deploymentTarget = \"10.15\"") + testImport() + + val tasks = listOf( + podspecTaskName, + defaultPodGenTaskName, + defaultSetupBuildTaskName, + defaultBuildTaskName, + defaultCinteropTaskName + ) + val anotherTarget = "MacosX64" + val anotherSdk = "macosx" + val anotherFamily = "macos" + buildGradleKts.addKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()") + + testImport { + assertTasksExecuted( + podGenFullTaskName(anotherFamily), + podSetupBuildFullTaskName(sdkName = anotherSdk), + podBuildFullTaskName(sdkName = anotherSdk), + cinteropFullTaskName(targetName = anotherTarget) + ) + assertTasksUpToDate(tasks) + } + + buildGradleKts.replaceText(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()", "") + testImport { + assertOutputDoesNotContain(podGenFullTaskName(anotherFamily)) + assertOutputDoesNotContain(podSetupBuildFullTaskName(sdkName = anotherSdk)) + assertOutputDoesNotContain(podBuildFullTaskName(sdkName = anotherSdk)) + assertOutputDoesNotContain(cinteropFullTaskName(targetName = anotherTarget)) + assertTasksUpToDate(tasks) + } + } + } + + @DisplayName("UTD build") + @GradleTest + fun testUTDBuild(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + buildGradleKts.addPod(defaultPodName, produceGitBlock()) + + testImport { + assertTasksExecuted(defaultBuildTaskName) + } + + val anotherTarget = "MacosX64" + val anotherSdk = "macosx" + val anotherSdkDefaultPodTaskName = podBuildFullTaskName(sdkName = anotherSdk) + buildGradleKts.addCocoapodsBlock("osx.deploymentTarget = \"10.15\"") + buildGradleKts.addKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()") + + testImport { + assertTasksUpToDate(defaultBuildTaskName) + assertTasksExecuted(anotherSdkDefaultPodTaskName) + } + testImport { + assertTasksUpToDate(defaultBuildTaskName, anotherSdkDefaultPodTaskName) + } + } + } + + @DisplayName("Pod Build UTD after clean") + @GradleTest + fun testPodBuildUTDClean(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + buildGradleKts.addPod(defaultPodName, produceGitBlock()) + + testImport { + assertTasksExecuted(defaultBuildTaskName) + } + build(":clean") + testImport { + assertTasksExecuted(defaultBuildTaskName) + } + } + } + + @DisplayName("Link with use of dynamic framework ") + @GradleTest + fun testUseDynamicFramework(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + buildGradleKts.addPod(defaultPodName, produceGitBlock()) + buildGradleKts.addFrameworkBlock("isStatic = false") + + build("linkPodDebugFrameworkIOS") { + val framework = projectPath.resolve("build/bin/iOS/podDebugFramework/cocoapods.framework/cocoapods") + val processRunResult = runProcess( + listOf("file", framework.absolutePathString()), + workingDir = projectPath.toFile(), + environmentVariables = environmentVariables.environmentalVariables + ) + assertProcessRunResult(processRunResult) { + assertTrue(isSuccessful) + assertTrue(output.contains("dynamically linked shared library")) + } + } + } + } + + @DisplayName("Link with use of static framework ") + @GradleTest + fun testUseStaticFramework(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + buildGradleKts.addPod(defaultPodName, produceGitBlock()) + buildGradleKts.addFrameworkBlock("isStatic = true") + + build("linkPodDebugFrameworkIOS") { + val framework = projectPath.resolve("build/bin/iOS/podDebugFramework/cocoapods.framework/cocoapods") + val processRunResult = runProcess( + listOf("file", framework.absolutePathString()), + workingDir = projectPath.toFile(), + environmentVariables = environmentVariables.environmentalVariables + ) + assertProcessRunResult(processRunResult) { + assertTrue(isSuccessful) + assertTrue(output.contains("current ar archive")) + } + } + } + } + + @DisplayName("UTD with different spec repo") + @GradleTest + fun testUTDPodGen(gradleVersion: GradleVersion) { + nativeProjectWithCocoapodsAndIosAppPodFile(gradleVersion = gradleVersion) { + + buildGradleKts.addPod(defaultPodName) + val repos = listOf( + "https://github.com/alozhkin/spec_repo_example", + "https://github.com/alozhkin/spec_repo_example_2" + ) + + isRepoAvailable(repos) + + build(defaultPodGenTaskName) { + assertTasksExecuted(defaultPodGenTaskName) + } + + buildGradleKts.addSpecRepo("https://github.com/alozhkin/spec_repo_example") + build(defaultPodGenTaskName) { + assertTasksExecuted(defaultPodGenTaskName) + } + + buildGradleKts.addSpecRepo("https://github.com/alozhkin/spec_repo_example_2") + build(defaultPodGenTaskName) { + assertTasksExecuted(defaultPodGenTaskName) + } + + build(defaultPodGenTaskName) { + assertTasksUpToDate(defaultPodGenTaskName) + } + } + + } + + private fun doTestGit( + gradleVersion: GradleVersion, + projectName: String = templateProjectName, + repo: String = defaultPodRepo, + pod: String = defaultPodName, + branch: String? = null, + commit: String? = null, + tag: String? = null, + isGradleBuildScript: Boolean = false, + testImportAssertions: BuildResult.() -> Unit = {}, + block: TestProject.() -> Unit = {}, + ) { + + nativeProjectWithCocoapodsAndIosAppPodFile(projectName, gradleVersion) { + val buildScript = if (isGradleBuildScript) buildGradle else buildGradleKts + buildScript.addPod(pod, produceGitBlock(repo, branch, commit, tag)) + + testImport(listOf(repo)) { + podImportAsserts(buildScript) + testImportAssertions() + } + block() + } + + } + + private fun produceGitBlock( + repo: String = defaultPodRepo, + branchName: String? = null, + commitName: String? = null, + tagName: String? = null, + ): String { + val branch = if (branchName != null) "branch = \"$branchName\"" else "" + val commit = if (commitName != null) "commit = \"$commitName\"" else "" + val tag = if (tagName != null) "tag = \"$tagName\"" else "" + return """source = git("$repo") { + | $branch + | $commit + | $tag + |} + """.trimMargin() + } + + private fun BuildResult.podImportAsserts( + buildScript: Path, + projectName: String? = null, + ) { + + val buildScriptText = buildScript.readText() + val taskPrefix = projectName?.let { ":$it" } ?: "" + val podspec = "podspec" + + if ("noPodspec()" in buildScriptText) { + assertTasksSkipped("$taskPrefix:$podspec") + } + + if ("podfile" in buildScriptText) { + assertTasksExecuted("$taskPrefix$podInstallTaskName") + } else { + assertTasksSkipped("$taskPrefix$podInstallTaskName") + } + if (buildScriptText.matches("pod\\(.*\\)".toRegex())) { + assertTasksExecuted(listOf("$taskPrefix:$POD_GEN_TASK_NAME")) + } + + with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_TASK_NAME).map { "$taskPrefix:$it" }) { + if (buildScriptText.matches("pod\\(.*\\)".toRegex())) { + assertTasksExecuted(this) + } + } + } + + private fun isRepoAvailable(repos: List) = runBlocking { + HttpClient(CIO).use { client -> + val nonAvailableRepos = repos + .map { repo -> + async { repo to runCatching { client.get(repo).status }.recover { it }.getOrNull() } + } + .awaitAll() + .filter { (_, status) -> status != HttpStatusCode.OK } + Assumptions.assumeTrue(nonAvailableRepos.isEmpty()) { + "The following repositories of ${repos.joinToString()} are not available: ${nonAvailableRepos.joinToString()}" + } + } + } + + private fun TestProject.testImport( + repos: List = listOf(defaultPodRepo), + vararg args: String, + assertions: BuildResult.() -> Unit = {}, + ) { + + isRepoAvailable(repos) + + build(podImportTaskName, *args) { + assertions() + } + } + + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt index 6e5dcb66ed7..4dfcf1bef54 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt @@ -13,7 +13,9 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME 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.util.createTempDir +import org.jetbrains.kotlin.gradle.testbase.ImportMode +import org.jetbrains.kotlin.gradle.testbase.cocoaPodsEnvironmentVariables +import org.jetbrains.kotlin.gradle.testbase.ensureCocoapodsInstalled import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.runProcess import org.jetbrains.kotlin.konan.target.HostManager @@ -22,7 +24,6 @@ import org.junit.Before import org.junit.BeforeClass import org.junit.Test import java.io.File -import java.io.IOException import java.util.* import java.util.concurrent.TimeUnit import java.util.zip.ZipFile @@ -40,7 +41,7 @@ class CocoaPodsIT : BaseGradleIT() { private val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT override fun defaultBuildOptions(): BuildOptions = - super.defaultBuildOptions().copy(customEnvironmentVariables = getEnvs()) + super.defaultBuildOptions().copy(customEnvironmentVariables = cocoaPodsEnvironmentVariables()) private val podfileImportDirectivePlaceholder = "" private val podfileImportPodPlaceholder = "#import_pod_directive" @@ -48,54 +49,24 @@ class CocoaPodsIT : BaseGradleIT() { private val cocoapodsSingleKtPod = "native-cocoapods-single" private val cocoapodsMultipleKtPods = "native-cocoapods-multiple" private val templateProjectName = "native-cocoapods-template" - private val groovyTemplateProjectName = "native-cocoapods-template-groovy" private val cocoapodsTestsProjectName = "native-cocoapods-tests" private val cocoapodsCommonizationProjectName = "native-cocoapods-commonization" private val dummyTaskName = ":$DUMMY_FRAMEWORK_TASK_NAME" private val podspecTaskName = ":$POD_SPEC_TASK_NAME" private val podGenTaskName = ":$POD_GEN_TASK_NAME" - private val podBuildTaskName = ":$POD_BUILD_TASK_NAME" - private val podSetupBuildTaskName = ":$POD_SETUP_BUILD_TASK_NAME" private val podImportTaskName = ":$POD_IMPORT_TASK_NAME" private val podInstallTaskName = ":$POD_INSTALL_TASK_NAME" - private val cinteropTaskName = ":cinterop" private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking" private val defaultPodName = "AFNetworking" - private val defaultTarget = "IOS" private val defaultFamily = "ios" - private val defaultSDK = "iphonesimulator" private val defaultPodGenTaskName = podGenFullTaskName() private val defaultPodInstallSyntheticTaskName = ":podInstallSyntheticIos" - private val defaultBuildTaskName = podBuildFullTaskName() - private val defaultSetupBuildTaskName = podSetupBuildFullTaskName() - private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget private fun podGenFullTaskName(familyName: String = defaultFamily) = podGenTaskName + familyName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } - private fun podSetupBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) = - podSetupBuildTaskName + podName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + sdkName.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() - } - - private fun podBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) = - podBuildTaskName + podName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + sdkName.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() - } - - private fun cinteropFullTaskName(podName: String = defaultPodName, targetName: String = defaultTarget) = - cinteropTaskName + podName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + targetName.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() - } - private lateinit var hooks: CustomHooks private lateinit var project: BaseGradleIT.Project @@ -154,7 +125,8 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun testSyntheticProjectPodfileGeneration() { val gradleProject = transformProjectWithPluginsDsl(cocoapodsSingleKtPod, gradleVersion) - gradleProject.gradleBuildScript().appendToCocoapodsBlock(""" + gradleProject.gradleBuildScript().appendToCocoapodsBlock( + """ ios.deploymentTarget = "14.1" pod("SSZipArchive") pod("AFNetworking", "~> 4.0.1") @@ -163,7 +135,8 @@ class CocoaPodsIT : BaseGradleIT() { tag = "5.6.1" } } - """.trimIndent()) + """.trimIndent() + ) gradleProject.build("podInstallSyntheticIos", "-Pkotlin.native.cocoapods.generate.wrapper=true") { assertSuccessful() assertTasksExecuted(":podGenIos") @@ -184,14 +157,16 @@ class CocoaPodsIT : BaseGradleIT() { project.gradleBuildScript().apply { appendToCocoapodsBlock("""pod("ChatSDK", version = "5.2.1")""") - appendText(""" + appendText( + """ tasks.withType().configureEach { doLast { podfile.get().appendText("ENV['SWIFT_VERSION'] = '5'") } } - """.trimIndent()) + """.trimIndent() + ) } project.build("podInstallSyntheticIos", "-Pkotlin.native.cocoapods.generate.wrapper=true") { @@ -200,65 +175,6 @@ class CocoaPodsIT : BaseGradleIT() { } } - @Test - fun testPodDownloadGitNoTagNorCommit() { - doTestGit() - } - - @Test - fun testPodDownloadGitTag() { - doTestGit(tag = "4.0.0") - } - - @Test - fun testPodDownloadGitCommit() { - doTestGit(commit = "9c07ac0a5645abb58850253eeb109ed0dca515c1") - } - - @Test - fun testPodDownloadGitBranch() { - doTestGit(branch = "2974") - } - - @Test - fun testPodDownloadGitSubspec() { - doTestGit( - repo = "https://github.com/SDWebImage/SDWebImage.git", - pod = "SDWebImage/MapKit", - tag = "5.9.2" - ) - } - - @Test - fun testPodDownloadGitBranchAndCommit() { - val branch = "2974" - val commit = "21637dd6164c0641e414bdaf3885af6f1ef15aee" - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo, branchName = branch, commitName = commit)) - } - project.testImportWithAsserts(listOf(defaultPodRepo)) - } - - // tag priority is bigger than branch priority - @Test - fun testPodDownloadGitBranchAndTag() { - val branch = "2974" - val tag = "4.0.0" - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo, branchName = branch, tagName = tag)) - } - project.testImportWithAsserts(listOf(defaultPodRepo)) - } - - @Test - fun testDownloadAndImport() { - val tag = "4.0.0" - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo, tagName = tag)) - } - project.testImportWithAsserts(listOf(defaultPodRepo)) - } - @Test fun warnIfDeprecatedPodspecPathIsUsed() { project = getProjectByName(cocoapodsSingleKtPod) @@ -306,13 +222,15 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun testImportUTDAfterLinkingFramework() { val linkTaskName = ":linkPodDebugFrameworkIOS" - project.gradleBuildScript().appendToCocoapodsBlock(""" + project.gradleBuildScript().appendToCocoapodsBlock( + """ framework { baseName = "kotlin-library" } name = "kotlin-library" podfile = project.file("ios-app/Podfile") - """.trimIndent()) + """.trimIndent() + ) hooks.addHook { @@ -335,13 +253,15 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun testChangeFrameworkTypeUTD() { - project.gradleBuildScript().appendToCocoapodsBlock(""" + project.gradleBuildScript().appendToCocoapodsBlock( + """ framework { baseName = "kotlin-library" } name = "kotlin-library" podfile = project.file("ios-app/Podfile") - """.trimIndent()) + """.trimIndent() + ) hooks.addHook { assertTasksExecuted(dummyTaskName) @@ -371,31 +291,6 @@ class CocoaPodsIT : BaseGradleIT() { } - - @Test - fun basicUTDTest() { - val tasks = listOf( - podspecTaskName, - defaultPodGenTaskName, - defaultPodInstallSyntheticTaskName, - defaultSetupBuildTaskName, - defaultBuildTaskName, - defaultCinteropTaskName, - ) - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - } - hooks.addHook { - assertTasksExecuted(tasks) - } - project.testImportWithAsserts(listOf(defaultPodRepo)) - - hooks.rewriteHooks { - assertTasksUpToDate(tasks) - } - project.testImport(listOf(defaultPodRepo)) - } - @Test fun testSpecReposUTD() { with(project.gradleBuildScript()) { @@ -433,53 +328,6 @@ class CocoaPodsIT : BaseGradleIT() { project.testSynthetic(defaultPodInstallSyntheticTaskName) } - @Test - fun testUTDPodAdded() { - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - } - project.testImport(listOf(defaultPodRepo)) - - val anotherPodName = "Alamofire" - val anotherPodRepo = "https://github.com/Alamofire/Alamofire" - with(project.gradleBuildScript()) { - addPod(anotherPodName, produceGitBlock(anotherPodRepo)) - } - hooks.rewriteHooks { - assertTasksExecuted( - podspecTaskName, - defaultPodGenTaskName, - podSetupBuildFullTaskName(anotherPodName), - podBuildFullTaskName(anotherPodName), - cinteropFullTaskName(anotherPodName) - ) - assertTasksUpToDate( - defaultSetupBuildTaskName, - defaultBuildTaskName, - defaultCinteropTaskName - ) - } - project.testImport(listOf(defaultPodRepo, anotherPodRepo)) - - with(project.gradleBuildScript()) { - removePod(anotherPodName) - } - hooks.rewriteHooks { - assertTasksNotRegisteredByPrefix( - listOf( - podBuildFullTaskName(anotherPodName), - cinteropFullTaskName(anotherPodName) - ) - ) - assertTasksUpToDate( - defaultBuildTaskName, - defaultSetupBuildTaskName, - defaultCinteropTaskName - ) - } - project.testImport(listOf(defaultPodRepo)) - } - @Test fun testImportSubspecs() { with(project.gradleBuildScript()) { @@ -489,62 +337,6 @@ class CocoaPodsIT : BaseGradleIT() { project.testImport(listOf(defaultPodRepo)) } - @Test - fun testUTDTargetAdded() { - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - appendToCocoapodsBlock("osx.deploymentTarget = \"10.15\"") - } - project.testImport(listOf(defaultPodRepo)) - - val anotherTarget = "MacosX64" - val anotherSdk = "macosx" - val anotherFamily = "macos" - with(project.gradleBuildScript()) { - appendToKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()") - } - hooks.rewriteHooks { - assertTasksExecuted( - podGenFullTaskName(anotherFamily), - podSetupBuildFullTaskName(sdkName = anotherSdk), - podBuildFullTaskName(sdkName = anotherSdk), - cinteropFullTaskName(targetName = anotherTarget) - ) - assertTasksUpToDate( - podspecTaskName, - defaultPodGenTaskName, - defaultSetupBuildTaskName, - defaultBuildTaskName, - defaultCinteropTaskName - ) - } - project.testImport(listOf(defaultPodRepo)) - - with(project.gradleBuildScript()) { - var text = readText() - text = text.replace(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()", "") - writeText(text) - } - hooks.rewriteHooks { - assertTasksNotRegisteredByPrefix( - listOf( - podGenFullTaskName(anotherFamily), - podSetupBuildFullTaskName(sdkName = anotherSdk), - podBuildFullTaskName(sdkName = anotherSdk), - cinteropFullTaskName(targetName = anotherTarget) - ) - ) - assertTasksUpToDate( - podspecTaskName, - defaultPodGenTaskName, - defaultSetupBuildTaskName, - defaultBuildTaskName, - defaultCinteropTaskName - ) - } - project.testImport(listOf(defaultPodRepo)) - } - @Test fun testUTDPodspec() { project.testWithWrapper(podspecTaskName) @@ -580,83 +372,6 @@ class CocoaPodsIT : BaseGradleIT() { project.testWithWrapper(podspecTaskName) } - @Test - fun testUTDPodGen() { - with(project.gradleBuildScript()) { - addPod(defaultPodName) - } - val repos = listOf( - "https://github.com/alozhkin/spec_repo_example", - "https://github.com/alozhkin/spec_repo_example_2" - ) - for (repo in repos) { - assumeTrue(isRepoAvailable(repo)) - } - hooks.addHook { - assertTasksExecuted(defaultPodGenTaskName) - } - project.testSynthetic(defaultPodGenTaskName) - with(project.gradleBuildScript()) { - addSpecRepo("https://github.com/alozhkin/spec_repo_example") - } - project.testSynthetic(defaultPodGenTaskName) - with(project.gradleBuildScript()) { - addSpecRepo("https://github.com/alozhkin/spec_repo_example_2") - } - project.testSynthetic(defaultPodGenTaskName) - hooks.rewriteHooks { - assertTasksUpToDate(defaultPodGenTaskName) - } - project.testSynthetic(defaultPodGenTaskName) - } - - @Test - fun testUTDBuild() { - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock()) - } - hooks.addHook { - assertTasksExecuted(defaultBuildTaskName) - } - project.testImport() - - val anotherTarget = "MacosX64" - val anotherSdk = "macosx" - with(project.gradleBuildScript()) { - appendToCocoapodsBlock("osx.deploymentTarget = \"10.15\"") - appendToKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()") - } - val anotherSdkDefaultPodTaskName = podBuildFullTaskName(sdkName = anotherSdk) - hooks.rewriteHooks { - assertTasksUpToDate(defaultBuildTaskName) - assertTasksExecuted(anotherSdkDefaultPodTaskName) - } - project.testImport() - - hooks.rewriteHooks { - assertTasksUpToDate(defaultBuildTaskName, anotherSdkDefaultPodTaskName) - } - project.testImport() - } - - @Test - fun testPodBuildUTDClean() { - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock()) - } - hooks.addHook { - assertTasksExecuted(defaultBuildTaskName) - } - project.testImport() - - hooks.rewriteHooks {} - project.test(":clean") - - hooks.addHook { - assertTasksExecuted(defaultBuildTaskName) - } - project.testImport() - } @Test fun testPodInstallWithoutPodFile() { @@ -664,19 +379,6 @@ class CocoaPodsIT : BaseGradleIT() { } - // groovy tests - - @Test - fun testGroovyDownloadAndImport() { - val project = getProjectByName(groovyTemplateProjectName) - val tag = "4.0.0" - with(project.gradleBuildScript()) { - addPod(defaultPodName, produceGitBlock(defaultPodRepo, tagName = tag)) - } - project.testImportWithAsserts(listOf(defaultPodRepo)) - } - - // other tests @Test @@ -742,47 +444,6 @@ class CocoaPodsIT : BaseGradleIT() { } } - @Test - fun testUseDynamicFramework() { - with(project) { - gradleBuildScript().addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - gradleBuildScript().appendToFrameworkBlock("isStatic = false") - hooks.addHook { - // Check that an output framework is a dynamic framework - val framework = fileInWorkingDir("build/bin/iOS/podDebugFramework/cocoapods.framework/cocoapods") - with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getEnvs())) { - assertTrue(isSuccessful) - assertTrue(output.contains("dynamically linked shared library")) - } - } - - test( - "linkPodDebugFrameworkIOS", - "-Pkotlin.native.cocoapods.generate.wrapper=true" - ) - } - } - - @Test - fun testUseStaticFramework() { - with(project) { - gradleBuildScript().addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - gradleBuildScript().appendToFrameworkBlock("isStatic = true") - hooks.addHook { - // Check that an output framework is a static framework - val framework = fileInWorkingDir("build/bin/iOS/podDebugFramework/cocoapods.framework/cocoapods") - with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getEnvs())) { - assertTrue(isSuccessful) - kotlin.test.assertContains(output, "current ar archive") - } - } - - test( - "linkPodDebugFrameworkIOS", - "-Pkotlin.native.cocoapods.generate.wrapper=true" - ) - } - } @Test fun testCocoapodsWithRegularFrameworkDefinition() { @@ -970,23 +631,6 @@ class CocoaPodsIT : BaseGradleIT() { getProjectByName(cocoapodsTestsProjectName).testWithWrapper(":iosX64Test") } - @Test - fun testCinteropUpToDate() { - project.gradleBuildScript().addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - project.testImport() - hooks.addHook { - assertTasksUpToDate( - defaultCinteropTaskName - ) - } - project.test( - "syncFramework", - "-Pkotlin.native.cocoapods.platform=iphonesimulator", - "-Pkotlin.native.cocoapods.archs=x86_64", - "-Pkotlin.native.cocoapods.configuration=Debug", - "-Pkotlin.native.cocoapods.generate.wrapper=true" - ) - } @Test fun testCinteropCommonizationOff() { @@ -1116,11 +760,13 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun testLinkOnlyPods() = with(project) { - gradleBuildScript().appendToCocoapodsBlock(""" + gradleBuildScript().appendToCocoapodsBlock( + """ pod("AFNetworking") { linkOnly = true } pod("SSZipArchive", linkOnly = true) pod("SDWebImage/Core") - """.trimIndent()) + """.trimIndent() + ) build( ":linkPodDebugFrameworkIOS", @@ -1136,31 +782,37 @@ class CocoaPodsIT : BaseGradleIT() { assertTasksNotRegistered(":cinteropAFNetworkingIOS") assertTasksNotRegistered(":cinteropSSZipArchiveIOS") - assertContains(""" + assertContains( + """ | -linker-option | -framework | -linker-option | AFNetworking - """.trimMargin()) + """.trimMargin() + ) - assertContains(""" + assertContains( + """ | -linker-option | -framework | -linker-option | SSZipArchive - """.trimMargin()) + """.trimMargin() + ) } } @Test fun testUsageLinkOnlyWithStaticFrameworkProducesMessage() = with(project) { - gradleBuildScript().appendToCocoapodsBlock(""" + gradleBuildScript().appendToCocoapodsBlock( + """ framework { isStatic = true } pod("AFNetworking") { linkOnly = true } - """.trimIndent()) + """.trimIndent() + ) build( ":linkPodDebugFrameworkIOS", @@ -1184,9 +836,11 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun `configuration fails when trying to depend on non-declared pod`() = with(getProjectByName("native-cocoapods-dependant-pods")) { - gradleBuildScript().appendToCocoapodsBlock(""" + gradleBuildScript().appendToCocoapodsBlock( + """ pod("Foo") { useInteropBindingFrom("JBNonExistent") } - """.trimIndent()) + """.trimIndent() + ) build( ":help", @@ -1199,10 +853,12 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun `configuration fails when dependant pods are in the wrong order`() = with(getProjectByName("native-cocoapods-dependant-pods")) { - gradleBuildScript().appendToCocoapodsBlock(""" + gradleBuildScript().appendToCocoapodsBlock( + """ pod("Foo") { useInteropBindingFrom("Bar") } pod("Bar") - """.trimIndent()) + """.trimIndent() + ) build( ":help", @@ -1215,9 +871,11 @@ class CocoaPodsIT : BaseGradleIT() { @Test fun `configuration fails when pod depends on itself`() = with(getProjectByName("native-cocoapods-dependant-pods")) { - gradleBuildScript().appendToCocoapodsBlock(""" + gradleBuildScript().appendToCocoapodsBlock( + """ pod("Foo") { useInteropBindingFrom("Foo") } - """.trimIndent()) + """.trimIndent() + ) build( ":help", @@ -1249,22 +907,10 @@ class CocoaPodsIT : BaseGradleIT() { } } - private fun doTestGit( - repo: String = defaultPodRepo, - pod: String = defaultPodName, - branch: String? = null, - commit: String? = null, - tag: String? = null - ) { - with(project.gradleBuildScript()) { - addPod(pod, produceGitBlock(repo, branch, commit, tag)) - } - project.testImportWithAsserts(listOf(repo)) - } private fun Project.testImportWithAsserts( repos: List = listOf(), - vararg args: String + vararg args: String, ) { hooks.addHook { podImportAsserts() @@ -1274,7 +920,7 @@ class CocoaPodsIT : BaseGradleIT() { private fun Project.testImport( repos: List = listOf(), - vararg args: String + vararg args: String, ) { for (repo in repos) { assumeTrue(isRepoAvailable(repo)) @@ -1284,21 +930,21 @@ class CocoaPodsIT : BaseGradleIT() { private fun Project.testSynthetic( taskName: String, - vararg args: String + vararg args: String, ) { testWithWrapper(taskName, *args) } private fun Project.testWithWrapper( taskName: String, - vararg args: String + vararg args: String, ) { test(taskName, "-Pkotlin.native.cocoapods.generate.wrapper=true", *args) } private fun Project.test( taskName: String, - vararg args: String + vararg args: String, ) { // check that test executable @@ -1320,25 +966,6 @@ class CocoaPodsIT : BaseGradleIT() { appendToCocoapodsBlock(podBlock) } - private fun File.removePod(podName: String) { - val text = readText() - val begin = text.indexOf("""pod("$podName")""") - require(begin != -1) { "Pod doesn't exist in file" } - var index = begin + """pod("$podName")""".length - 1 - if (text.indexOf("""pod("$podName") {""", startIndex = begin) != -1) { - index += 2 - var bracket = 1 - while (bracket != 0) { - if (text[++index] == '{') { - bracket++ - } else if (text[index] == '}') { - bracket-- - } - } - } - writeText(text.removeRange(begin..index)) - } - private fun File.addSpecRepo(specRepo: String) = appendToCocoapodsBlock("url(\"$specRepo\")".wrap("specRepos")) private fun File.appendToKotlinBlock(str: String) = appendLine(str.wrap("kotlin")) @@ -1355,23 +982,6 @@ class CocoaPodsIT : BaseGradleIT() { private fun File.appendLine(s: String) = appendText("\n$s") - private fun produceGitBlock( - repo: String = defaultPodRepo, - branchName: String? = null, - commitName: String? = null, - tagName: String? = null - ): String { - val branch = if (branchName != null) "branch = \"$branchName\"" else "" - val commit = if (commitName != null) "commit = \"$commitName\"" else "" - val tag = if (tagName != null) "tag = \"$tagName\"" else "" - return """source = git("$repo") { - | $branch - | $commit - | $tag - |} - """.trimMargin() - } - // proposition phase @@ -1426,15 +1036,10 @@ class CocoaPodsIT : BaseGradleIT() { } } - private enum class ImportMode(val directive: String) { - FRAMEWORKS("use_frameworks!"), - MODULAR_HEADERS("use_modular_headers!") - } - private data class CommandResult( val exitCode: Int, val stdOut: String, - val stdErr: String + val stdErr: String, ) private fun runCommand( @@ -1443,11 +1048,11 @@ class CocoaPodsIT : BaseGradleIT() { vararg args: String, timeoutSec: Long = 120, inheritIO: Boolean = false, - block: CommandResult.() -> Unit + block: CommandResult.() -> Unit, ) { val process = ProcessBuilder(command, *args).apply { directory(workingDir) - environment().putAll(getEnvs()) + environment().putAll(cocoaPodsEnvironmentVariables()) if (inheritIO) { inheritIO() } @@ -1523,79 +1128,12 @@ class CocoaPodsIT : BaseGradleIT() { @BeforeClass @JvmStatic - fun ensureCocoapodsInstalled() { + fun checkCocoapodsInstalled() { if (!HostManager.hostIsMac) { return } - if (shouldInstallLocalCocoapods) { - val installDir = cocoapodsInstallationRoot.absolutePath - println("Installing CocoaPods...") - - //https://github.com/ffi/ffi/issues/864#issuecomment-875242776 - gem("install", "--install-dir", installDir, "ffi", "-v", "1.15.5", "--", "--enable-libffi-alloc") - - gem("install", "--install-dir", installDir, "cocoapods", "-v", localCocoapodsVersion) - } else if (!isCocoapodsInstalled()) { - fail( - """ - Running CocoaPods integration tests requires cocoapods to be installed. - Please install them manually: - gem install cocoapods - Or re-run the tests with the 'installCocoapods=true' Gradle property. - """.trimIndent() - ) - } - } - - private const val localCocoapodsVersion = "1.11.0" - - private val shouldInstallLocalCocoapods: Boolean = System.getProperty("installCocoapods").toBoolean() - - private val cocoapodsInstallationRoot: File by lazy { createTempDir("cocoapods") } - private val cocoapodsBinPath: File by lazy { - cocoapodsInstallationRoot.resolve("bin") - } - - private fun getEnvs(): Map { - if (!shouldInstallLocalCocoapods) { - return emptyMap() - } - - val path = cocoapodsBinPath.absolutePath + File.pathSeparator + System.getenv("PATH") - val gemPath = System.getenv("GEM_PATH")?.let { - cocoapodsInstallationRoot.absolutePath + File.pathSeparator + it - } ?: cocoapodsInstallationRoot.absolutePath - return mapOf( - "PATH" to path, - "GEM_PATH" to gemPath, - // CocoaPods 1.11 requires UTF-8 locale being set, more details: https://github.com/CocoaPods/CocoaPods/issues/10939 - "LC_ALL" to "en_US.UTF-8" - ) - } - - private fun isCocoapodsInstalled(): Boolean { - // Do not use 'gem list' because the gem may be installed but PATH may miss its executables. - // Try to access the pod executable directly instead - return try { - val result = runProcess( - listOf("pod", "--version"), - File("."), - ) - result.isSuccessful - } catch (e: IOException) { - false - } - } - - private fun gem(vararg args: String): String { - val command = listOf("gem", *args) - println("Run command: ${command.joinToString(separator = " ")}") - val result = runProcess(command, File("."), options = BuildOptions(forceOutputToStdout = true)) - check(result.isSuccessful) { - "Process 'gem ${args.joinToString(separator = " ")}' exited with error code ${result.exitCode}. See log for details." - } - return result.output + ensureCocoapodsInstalled() } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsXcodeIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsXcodeIT.kt index 92ca0b88dbd..3f237418d7a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsXcodeIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsXcodeIT.kt @@ -6,10 +6,11 @@ package org.jetbrains.kotlin.gradle.native import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.assertProcessRunResult -import org.jetbrains.kotlin.gradle.util.replaceText import org.jetbrains.kotlin.gradle.util.runProcess +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.condition.OS import java.nio.file.Path @@ -24,18 +25,16 @@ import kotlin.test.assertEquals @OptIn(EnvironmentalVariablesOverride::class) class CocoaPodsXcodeIT : KGPBaseTest() { - private val podfileImportDirectivePlaceholder = "" - private val cocoapodsSingleKtPod = "native-cocoapods-single" private val cocoapodsMultipleKtPods = "native-cocoapods-multiple" private val templateProjectName = "native-cocoapods-template" - private val environmentVariables = EnvironmentalVariables( - mapOf( - // CocoaPods 1.11 requires UTF-8 locale being set, more details: https://github.com/CocoaPods/CocoaPods/issues/10939 - "LC_ALL" to "en_US.UTF-8" - ) - ) + private val environmentVariables = EnvironmentalVariables(cocoaPodsEnvironmentVariables()) + + @BeforeAll + fun setUp() { + ensureCocoapodsInstalled() + } @DisplayName("Checks xcodebuild for ios-app with a single framework") @GradleTest @@ -142,20 +141,6 @@ class CocoaPodsXcodeIT : KGPBaseTest() { mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary") ) - private enum class ImportMode(val directive: String) { - FRAMEWORKS("use_frameworks!"), - MODULAR_HEADERS("use_modular_headers!") - } - - private fun TestProject.preparePodfile(iosAppLocation: String, mode: ImportMode) { - val iosAppDir = projectPath.resolve(iosAppLocation) - - // Set import mode for Podfile. - iosAppDir.resolve("Podfile") - .takeIf { it.exists() } - ?.replaceText(podfileImportDirectivePlaceholder, mode.directive) - } - private fun doTestXcode( projectName: String, gradleVersion: GradleVersion, @@ -208,7 +193,7 @@ class CocoaPodsXcodeIT : KGPBaseTest() { // Set import mode for Podfile. preparePodfile(it, mode) // Install pods. - build("$taskPrefix:podInstall", buildOptions = buildOptions) + build("$taskPrefix:$POD_INSTALL_TASK_NAME", buildOptions = buildOptions) projectPath.resolve(it).apply { // Run Xcode build. diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index 9c23722e665..0d02d9d9a46 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -68,6 +68,9 @@ data class BuildOptions( data class NativeOptions( val cacheKind: NativeCacheKind = NativeCacheKind.NONE, val cocoapodsGenerateWrapper: Boolean? = null, + val cocoapodsPlatform: String? = null, + val cocoapodsConfiguration: String? = null, + val cocoapodsArchs: String? = null, val distributionType: String? = null, val distributionDownloadFromMaven: Boolean? = null, val platformLibrariesMode: String? = null, @@ -199,6 +202,15 @@ data class BuildOptions( nativeOptions.cocoapodsGenerateWrapper?.let { arguments.add("-Pkotlin.native.cocoapods.generate.wrapper=${it}") } + nativeOptions.cocoapodsPlatform?.let { + arguments.add("-Pkotlin.native.cocoapods.platform=${it}") + } + nativeOptions.cocoapodsArchs?.let { + arguments.add("-Pkotlin.native.cocoapods.archs=${it}") + } + nativeOptions.cocoapodsConfiguration?.let { + arguments.add("-Pkotlin.native.cocoapods.configuration=${it}") + } nativeOptions.distributionDownloadFromMaven?.let { arguments.add("-Pkotlin.native.distribution.downloadFromMaven=${it}") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/TestVersions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/TestVersions.kt index 7ea675577cc..ec0ca4eb417 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/TestVersions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/TestVersions.kt @@ -70,6 +70,10 @@ interface TestVersions { ; } + object COCOAPODS { + const val VERSION = "1.11.0" + } + object AppleGradlePlugin { const val V222_0_21 = "222.4550-0.21" } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/cocoapodsTestHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/cocoapodsTestHelpers.kt index 1f4ddf9b9c0..6d1241e04cc 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/cocoapodsTestHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/cocoapodsTestHelpers.kt @@ -5,13 +5,25 @@ package org.jetbrains.kotlin.gradle.testbase +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.BaseGradleIT +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME import org.jetbrains.kotlin.gradle.util.replaceText +import org.jetbrains.kotlin.gradle.util.runProcess +import java.io.File +import java.io.IOException import java.nio.file.Path -import kotlin.io.path.appendText +import kotlin.io.path.* +import kotlin.test.fail val String.normalizeCocoapadsFrameworkName: String get() = replace('-', '_') +enum class ImportMode(val directive: String) { + FRAMEWORKS("use_frameworks!"), + MODULAR_HEADERS("use_modular_headers!") +} + fun TestProject.useCustomCocoapodsFrameworkName( subprojectName: String, frameworkName: String, @@ -34,10 +46,189 @@ fun TestProject.useCustomCocoapodsFrameworkName( } } +/** + * Prepares the Podfile for an iOS app in the [TestProject] + * + * @param iosAppLocation The relative location of the iOS app directory within the [TestProject] + * @param mode The [ImportMode] to be set for the Podfile. + * + */ +fun TestProject.preparePodfile(iosAppLocation: String, mode: ImportMode) { + val iosAppDir = projectPath.resolve(iosAppLocation) + + // Set import mode for Podfile. + iosAppDir.resolve("Podfile") + .takeIf { it.exists() } + ?.replaceText(podfileImportDirectivePlaceholder, mode.directive) +} + +/** + * Wraps the given string into a specRepos block and adds this block to the end of the [this] path. + * + * @param specRepo The code to be wrapped with the Cocoapods block. + */ + +fun Path.addSpecRepo(specRepo: String) = addCocoapodsBlock("url(\"$specRepo\")".wrapIntoBlock("specRepos")) + +/** + * Wraps the given string into a Cocoapods block and adds this block to the end of the [this] path. + * + * @param str The code to be wrapped with the Cocoapods block. + */ fun Path.addCocoapodsBlock(str: String) = addKotlinBlock(str.wrapIntoBlock("cocoapods")) -private fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin")) +/** + * Wraps the given string into a Kotlin block and adds this block to the end of the [this] path. + * + * @param str The code to be wrapped with the Cocoapods block. + */ +fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin")) -private fun Path.addFrameworkBlock(str: String) = addCocoapodsBlock(str.wrapIntoBlock("framework")) +/** + * Wraps the given string into a Framework block and adds this block to the end of the [this] path. + * + * @param str The code to be wrapped with the Cocoapods block. + */ +fun Path.addFrameworkBlock(str: String) = addCocoapodsBlock(str.wrapIntoBlock("framework")) -private fun Path.appendLine(s: String) = appendText("\n$s") \ No newline at end of file + +/** + * Adds a Cocoapods dependency to [this] build script. + * + * @param podName The name of the Cocoapods dependency to be added. + * @param configuration The optional configuration string for the Cocoapods dependency. + */ +fun Path.addPod(podName: String, configuration: String? = null) { + val pod = "pod(\"$podName\")" + val podBlock = configuration?.wrapIntoBlock(pod) ?: pod + addCocoapodsBlock(podBlock) +} + +/** + * Removes a Cocoapods dependency from [this] build script. + * + * @param podName The name of the Cocoapods dependency to be removes. + */ +fun Path.removePod(podName: String) { + val text = readText() + val begin = text.indexOf("""pod("$podName")""") + require(begin != -1) { + """ + Pod doesn't exist in file. File content is: + ${text} + """.trimIndent() + } + var index = begin + """pod("$podName")""".length - 1 + if (text.indexOf("""pod("$podName") {""", startIndex = begin) != -1) { + index += 2 + var bracket = 1 + while (bracket != 0) { + if (text[++index] == '{') { + bracket++ + } else if (text[index] == '}') { + bracket-- + } + } + } + writeText(text.removeRange(begin..index)) +} + +/** + * Method returns required environment variables for cocoapods tests with execution of [POD_INSTALL_TASK_NAME] + */ +fun cocoaPodsEnvironmentVariables(): Map { + if (!shouldInstallLocalCocoapods) { + return emptyMap() + } + + val path = cocoapodsBinPath.absolutePathString() + File.pathSeparator + System.getenv("PATH") + val gemPath = System.getenv("GEM_PATH")?.let { + cocoapodsInstallationRoot.absolutePathString() + File.pathSeparator + it + } ?: cocoapodsInstallationRoot.absolutePathString() + return mapOf( + "PATH" to path, + "GEM_PATH" to gemPath, + // CocoaPods 1.11 requires UTF-8 locale being set, more details: https://github.com/CocoaPods/CocoaPods/issues/10939 + "LC_ALL" to "en_US.UTF-8" + ) +} + +/** + * This method checks if Cocoapods should be installed and verifies its installation status. + * If [shouldInstallLocalCocoapods] is true, it tries to install Cocoapods into the specified [cocoapodsInstallationRoot] + * if it is not already installed. + * + * @throws AssertionError if [shouldInstallLocalCocoapods] is false and cocoapods has not been installed + */ +fun ensureCocoapodsInstalled() { + if (shouldInstallLocalCocoapods) { + val installDir = cocoapodsInstallationRoot.absolutePathString() + println("Installing CocoaPods...") + + //https://github.com/ffi/ffi/issues/864#issuecomment-875242776 + gem("install", "--install-dir", installDir, "ffi", "-v", "1.15.5", "--", "--enable-libffi-alloc") + + gem("install", "--install-dir", installDir, "cocoapods", "-v", TestVersions.COCOAPODS.VERSION) + } else if (!isCocoapodsInstalled()) { + fail( + """ + Running CocoaPods integration tests requires cocoapods to be installed. + Please install them manually: + gem install cocoapods + Or re-run the tests with the 'installCocoapods=true' Gradle property. + """.trimIndent() + ) + } +} + +@EnvironmentalVariablesOverride +fun KGPBaseTest.nativeProjectWithCocoapodsAndIosAppPodFile( + projectName: String = templateProjectName, + gradleVersion: GradleVersion, + buildOptions: BuildOptions = this.defaultBuildOptions, + projectBlock: TestProject.() -> Unit = {}, +) { + nativeProject( + projectName, + gradleVersion, + buildOptions = buildOptions, + environmentVariables = EnvironmentalVariables(cocoaPodsEnvironmentVariables()) + ) { + preparePodfile("ios-app", ImportMode.FRAMEWORKS) + projectBlock() + } +} + +private val templateProjectName = "native-cocoapods-template" + +private val shouldInstallLocalCocoapods: Boolean = System.getProperty("installCocoapods").toBoolean() +private val cocoapodsInstallationRoot: Path by lazy { createTempDirectory("cocoapods") } +private val cocoapodsBinPath: Path by lazy { cocoapodsInstallationRoot.resolve("bin") } + +private fun isCocoapodsInstalled(): Boolean { + // Do not use 'gem list' because the gem may be installed but PATH may miss its executables. + // Try to access the pod executable directly instead + return try { + val result = runProcess( + listOf("pod", "--version"), + File("."), + ) + result.isSuccessful + } catch (e: IOException) { + false + } +} + +private fun gem(vararg args: String): String { + val command = listOf("gem", *args) + println("Run command: ${command.joinToString(separator = " ")}") + val result = runProcess(command, File("."), options = BaseGradleIT.BuildOptions(forceOutputToStdout = true)) + check(result.isSuccessful) { + "Process 'gem ${args.joinToString(separator = " ")}' exited with error code ${result.exitCode}. See log for details." + } + return result.output +} + +private fun Path.appendLine(s: String) = appendText("\n$s") + +private const val podfileImportDirectivePlaceholder = "" \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/appleGradlePluginConsumesAppleFrameworks/iosApp/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/appleGradlePluginConsumesAppleFrameworks/iosApp/build.gradle.kts index 9397708343d..af9dbbfaaaf 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/appleGradlePluginConsumesAppleFrameworks/iosApp/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/appleGradlePluginConsumesAppleFrameworks/iosApp/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget plugins { - id("org.jetbrains.gradle.apple.applePlugin") version "222.4550-0.21" + id("org.jetbrains.gradle.apple.applePlugin") version } apple { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template-groovy/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template-groovy/build.gradle index 1c0730892fd..30a223b6aaa 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template-groovy/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template-groovy/build.gradle @@ -1,6 +1,6 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") - id("org.jetbrains.kotlin.native.cocoapods").version("") + id("org.jetbrains.kotlin.multiplatform") + id("org.jetbrains.kotlin.native.cocoapods") } group = "com.example"