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 eb8e73d2cd3..6e5dcb66ed7 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,8 +13,6 @@ 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.testbase.TestVersions -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 @@ -109,58 +107,6 @@ class CocoaPodsIT : BaseGradleIT() { } } - @Test - fun testXcodeUseFrameworksSingle() = doTestXcode( - cocoapodsSingleKtPod, - ImportMode.FRAMEWORKS, - "ios-app", mapOf("kotlin-library" to null) - ) - - @Test - fun testXcodeUseFrameworksWithCustomFrameworkNameSingle() = doTestXcode( - cocoapodsSingleKtPod, - ImportMode.FRAMEWORKS, - "ios-app", - mapOf("kotlin-library" to "MultiplatformLibrary") - ) - - @Test - fun testXcodeUseModularHeadersSingle() = doTestXcode( - cocoapodsSingleKtPod, - ImportMode.MODULAR_HEADERS, - "ios-app", - mapOf("kotlin-library" to null) - ) - - @Test - fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle() = doTestXcode( - cocoapodsSingleKtPod, - ImportMode.MODULAR_HEADERS, - "ios-app", - mapOf("kotlin-library" to "MultiplatformLibrary") - ) - - @Test - fun testXcodeBuildsWithKotlinLibraryFromRootProject() { - - val project = transformProjectWithPluginsDsl(templateProjectName, GradleVersionRequired.AtLeast(TestVersions.Gradle.G_7_6)) - - project.gradleBuildScript().appendToCocoapodsBlock(""" - framework { - baseName = "kotlin-library" - } - name = "kotlin-library" - podfile = project.file("ios-app/Podfile") - """.trimIndent()) - - doTestXcode( - project, - ImportMode.FRAMEWORKS, - "ios-app", - mapOf("" to null), - ) - } - @Test fun testPodImportSingle() { val project = getProjectByName(cocoapodsSingleKtPod) @@ -194,38 +140,6 @@ class CocoaPodsIT : BaseGradleIT() { project.testSynthetic(":second-library:podImport") } - @Test - fun testXcodeUseFrameworksMultiple() = doTestXcode( - cocoapodsMultipleKtPods, - ImportMode.FRAMEWORKS, - null, - mapOf("kotlin-library" to null, "second-library" to null) - ) - - @Test - fun testXcodeUseFrameworksWithCustomFrameworkNameMultiple() = doTestXcode( - cocoapodsMultipleKtPods, - ImportMode.FRAMEWORKS, - null, - mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary") - ) - - @Test - fun testXcodeUseModularHeadersMultiple() = doTestXcode( - cocoapodsMultipleKtPods, - ImportMode.MODULAR_HEADERS, - null, - mapOf("kotlin-library" to null, "second-library" to null) - ) - - @Test - fun testXcodeUseModularHeadersWithCustomFrameworkNameMultiple() = doTestXcode( - cocoapodsMultipleKtPods, - ImportMode.MODULAR_HEADERS, - null, - mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary") - ) - @Test fun testSpecReposImport() { val podName = "example" @@ -1512,19 +1426,6 @@ class CocoaPodsIT : BaseGradleIT() { } } - private fun Project.useCustomFrameworkName(subproject: String, frameworkName: String, iosAppLocation: String? = null) { - // Change the name at the Gradle side. - gradleBuildScript(subproject).appendToFrameworkBlock("baseName = \"$frameworkName\"") - - // Change swift sources import if needed. - if (iosAppLocation != null) { - val iosAppDir = projectDir.resolve(iosAppLocation) - iosAppDir.resolve("ios-app/ViewController.swift").modify { - it.replace("import ${subproject.normalizeCocoapadsFrameworkName}", "import $frameworkName") - } - } - } - private enum class ImportMode(val directive: String) { FRAMEWORKS("use_frameworks!"), MODULAR_HEADERS("use_modular_headers!") @@ -1565,90 +1466,6 @@ class CocoaPodsIT : BaseGradleIT() { CommandResult(process.exitValue(), stdOut, stdErr).block() } - private fun doTestXcode( - projectName: String, - mode: ImportMode, - iosAppLocation: String?, - subprojectsToFrameworkNamesMap: Map, - ) { - val gradleProject = transformProjectWithPluginsDsl(projectName, gradleVersion) - - doTestXcode( - gradleProject = gradleProject, - mode = mode, - iosAppLocation = iosAppLocation, - subprojectsToFrameworkNamesMap = subprojectsToFrameworkNamesMap, - ) - } - - private fun doTestXcode( - gradleProject: Project, - mode: ImportMode, - iosAppLocation: String?, - subprojectsToFrameworkNamesMap: Map, - arch: String = "x86_64", - ) { - - gradleProject.projectDir.resolve("gradle.properties") - .takeIf(File::exists) - ?.let { - it.appendLine("kotlin_version=${defaultBuildOptions().kotlinVersion}") - it.appendLine("test_fixes_version=${defaultBuildOptions().kotlinVersion}") - } - - with(gradleProject) { - setupWorkingDir() - - for ((subproject, frameworkName) in subprojectsToFrameworkNamesMap) { - - val taskPrefix = if (subproject.isNotEmpty()) ":$subproject" else "" - - // Add property with custom framework name - frameworkName?.let { - useCustomFrameworkName(subproject, it, iosAppLocation) - } - - // Generate podspec. - build("$taskPrefix:podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true") { - assertSuccessful() - } - iosAppLocation?.also { - // Set import mode for Podfile. - preparePodfile(it, mode) - // Install pods. - build("$taskPrefix:podInstall", "-Pkotlin.native.cocoapods.generate.wrapper=true") { - assertSuccessful() - } - - projectDir.resolve(it).apply { - // Run Xcode build. - runCommand( - this, "xcodebuild", - "-sdk", "iphonesimulator", - "-configuration", "Release", - "-workspace", "$name.xcworkspace", - "-scheme", name, - "-arch", arch, - inheritIO = true // Xcode doesn't finish the process if the PIPE redirect is used. - ) { - assertEquals( - 0, exitCode, """ - |Exit code mismatch for `xcodebuild`. - |stdout: - |$stdOut - | - |stderr: - |$stdErr - """.trimMargin() - ) - } - } - } - } - - } - } - private fun Project.preparePodfile(iosAppLocation: String, mode: ImportMode) { val iosAppDir = projectDir.resolve(iosAppLocation) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsPodspecIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsPodspecIT.kt index b277eb7d44c..fc8dbe64322 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsPodspecIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsPodspecIT.kt @@ -13,7 +13,7 @@ import kotlin.io.path.readText import kotlin.test.assertEquals @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) -@DisplayName("K/N tests cocoapods") +@DisplayName("K/N tests with cocoapods' podspec") @NativeGradlePluginTests class CocoaPodsPodspecIT : KGPBaseTest() { 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 new file mode 100644 index 00000000000..92ca0b88dbd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsXcodeIT.kt @@ -0,0 +1,235 @@ +/* + * 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.jetbrains.kotlin.gradle.util.assertProcessRunResult +import org.jetbrains.kotlin.gradle.util.replaceText +import org.jetbrains.kotlin.gradle.util.runProcess +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.condition.OS +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.name +import kotlin.test.assertEquals + +@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) +@DisplayName("K/N tests cocoapods with xcodebuild") +@NativeGradlePluginTests +@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) +@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" + ) + ) + + @DisplayName("Checks xcodebuild for ios-app with a single framework") + @GradleTest + fun testXcodeUseFrameworksSingle(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsSingleKtPod, + gradleVersion, + ImportMode.FRAMEWORKS, + "ios-app", + mapOf("kotlin-library" to null) + ) + + @DisplayName("Checks xcodebuild for ios-app with a single framework with custom name") + @GradleTest + fun testXcodeUseFrameworksWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsSingleKtPod, + gradleVersion, + ImportMode.FRAMEWORKS, + "ios-app", + mapOf("kotlin-library" to "MultiplatformLibrary") + ) + + @DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework") + @GradleTest + fun testXcodeUseModularHeadersSingle(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsSingleKtPod, + gradleVersion, + ImportMode.MODULAR_HEADERS, + "ios-app", + mapOf("kotlin-library" to null) + ) + + + @DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework with custom name") + @GradleTest + fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsSingleKtPod, + gradleVersion, + ImportMode.MODULAR_HEADERS, + "ios-app", + mapOf("kotlin-library" to "MultiplatformLibrary") + ) + + @DisplayName("Checks xcodebuild for ios-app with kotlin library from root project") + @GradleTest + fun testXcodeBuildsWithKotlinLibraryFromRootProject(gradleVersion: GradleVersion) { + + nativeProject(templateProjectName, gradleVersion, environmentVariables = environmentVariables) { + + buildGradleKts.addCocoapodsBlock( + """ + framework { + baseName = "kotlin-library" + } + name = "kotlin-library" + podfile = project.file("ios-app/Podfile") + """.trimIndent() + ) + + doTestXcode( + ImportMode.FRAMEWORKS, + "ios-app", + mapOf("" to null), + ) + } + } + + @DisplayName("Checks xcodebuild for two libraries with framework") + @GradleTest + fun testXcodeUseFrameworksMultiple(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsMultipleKtPods, + gradleVersion, + ImportMode.FRAMEWORKS, + null, + mapOf("kotlin-library" to null, "second-library" to null) + ) + + @DisplayName("Checks xcodebuild for two libraries with framework and custom names") + @GradleTest + fun testXcodeUseFrameworksWithCustomFrameworkNameMultiple(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsMultipleKtPods, + gradleVersion, + ImportMode.FRAMEWORKS, + null, + mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary") + ) + + @DisplayName("Checks xcodebuild for two libraries with modular headers") + @GradleTest + fun testXcodeUseModularHeadersMultiple(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsMultipleKtPods, + gradleVersion, + ImportMode.MODULAR_HEADERS, + null, + mapOf("kotlin-library" to null, "second-library" to null) + ) + + @DisplayName("Checks xcodebuild for two libraries with modular headers and custom names") + @GradleTest + fun testXcodeUseModularHeadersWithCustomFrameworkNameMultiple(gradleVersion: GradleVersion) = doTestXcode( + cocoapodsMultipleKtPods, + gradleVersion, + ImportMode.MODULAR_HEADERS, + null, + 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, + mode: ImportMode, + iosAppLocation: String?, + subprojectsToFrameworkNamesMap: Map, + ) { + + nativeProject(projectName, gradleVersion, environmentVariables = environmentVariables) { + doTestXcode( + mode = mode, + iosAppLocation = iosAppLocation, + subprojectsToFrameworkNamesMap = subprojectsToFrameworkNamesMap, + ) + } + } + + private fun TestProject.doTestXcode( + mode: ImportMode, + iosAppLocation: String?, + subprojectsToFrameworkNamesMap: Map, + arch: String = "x86_64", + ) { + + gradleProperties + .takeIf(Path::exists) + ?.let { + it.append("kotlin_version=${defaultBuildOptions.kotlinVersion}") + it.append("test_fixes_version=${defaultBuildOptions.kotlinVersion}") + } + + for ((subproject, frameworkName) in subprojectsToFrameworkNamesMap) { + + val taskPrefix = if (subproject.isNotEmpty()) ":$subproject" else "" + + // Add property with custom framework name + frameworkName?.let { + useCustomCocoapodsFrameworkName(subproject, it, iosAppLocation) + } + + val buildOptions = defaultBuildOptions.copy( + nativeOptions = defaultBuildOptions.nativeOptions.copy( + cocoapodsGenerateWrapper = true + ) + ) + + // Generate podspec. + build("$taskPrefix:podspec", buildOptions = buildOptions) + iosAppLocation?.also { + // Set import mode for Podfile. + preparePodfile(it, mode) + // Install pods. + build("$taskPrefix:podInstall", buildOptions = buildOptions) + + projectPath.resolve(it).apply { + // Run Xcode build. + val xcodebuildResult = runProcess( + cmd = listOf( + "xcodebuild", + "-sdk", "iphonesimulator", + "-configuration", "Release", + "-workspace", "$name.xcworkspace", + "-scheme", name, + "-arch", arch + ), + environmentVariables = environmentVariables.environmentalVariables, + workingDir = this.toFile(), + ) + assertProcessRunResult(xcodebuildResult) { + assertEquals(0, exitCode, "Exit code mismatch for `xcodebuild`.") + } + } + } + } + + } +} \ No newline at end of file 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 a9ee91fc84a..1f4ddf9b9c0 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 @@ -34,9 +34,9 @@ fun TestProject.useCustomCocoapodsFrameworkName( } } -private fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin")) +fun Path.addCocoapodsBlock(str: String) = addKotlinBlock(str.wrapIntoBlock("cocoapods")) -private fun Path.addCocoapodsBlock(str: String) = addKotlinBlock(str.wrapIntoBlock("cocoapods")) +private fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin")) private fun Path.addFrameworkBlock(str: String) = addCocoapodsBlock(str.wrapIntoBlock("framework")) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt index e236af4dfb6..af9d4056526 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt @@ -131,7 +131,6 @@ fun KGPBaseTest.nativeProject( /** * Trigger test project build with given [buildArguments] and assert build is successful. */ -@OptIn(EnvironmentalVariablesOverride::class) fun TestProject.build( vararg buildArguments: String, forceOutput: Boolean = this.forceOutput, diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt index 0dc43d7871d..c39b3e71573 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt @@ -13,7 +13,8 @@ class ProcessRunResult( private val cmd: List, private val workingDir: File, val exitCode: Int, - val output: String + val output: String, + val stdErr: String, ) { val isSuccessful: Boolean get() = exitCode == 0 @@ -47,9 +48,10 @@ fun runProcess( } sb.append(it).append(SYSTEM_LINE_SEPARATOR) } + val stdErr = process.errorStream.bufferedReader().use { it.readText() } val exitCode = process.waitFor() - return ProcessRunResult(cmd, workingDir, exitCode, sb.toString()) + return ProcessRunResult(cmd, workingDir, exitCode, sb.toString(), stdErr) } fun createGradleCommand(wrapperDir: File, tailParameters: List): List { @@ -86,6 +88,11 @@ fun assertProcessRunResult(result: ProcessRunResult, assertions: ProcessRunResul |${result.output} |####################### | + |Process error output: + |####################### + |${result.stdErr} + |####################### + | """.trimMargin() ) throw e diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template/build.gradle.kts index c8809358b34..7986cfea7ed 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-template/build.gradle.kts @@ -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"