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 ea7a2d8ffb0..8a2c2fbf419 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 @@ -17,6 +17,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.transformProjectWithPluginsDsl +import org.jetbrains.kotlin.gradle.util.createTempDir import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.runProcess import org.jetbrains.kotlin.konan.target.HostManager @@ -26,6 +27,7 @@ import org.junit.Before import org.junit.BeforeClass import org.junit.Test import java.io.File +import java.io.IOException import java.nio.file.Files import java.nio.file.Path import java.util.concurrent.TimeUnit @@ -50,7 +52,7 @@ class CocoaPodsIT : BaseGradleIT() { private val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT override fun defaultBuildOptions(): BuildOptions = - super.defaultBuildOptions().copy(customEnvironmentVariables = mapOf("PATH" to getPathEnv())) + super.defaultBuildOptions().copy(customEnvironmentVariables = getPathEnvs()) val PODFILE_IMPORT_DIRECTIVE_PLACEHOLDER = "" @@ -770,8 +772,7 @@ class CocoaPodsIT : BaseGradleIT() { hooks.addHook { // Check that a built universal framework includes both device and simulator architectures. val framework = fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods") - val env = mapOf("PATH" to getPathEnv()) - with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = env)) { + with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getPathEnvs())) { assertTrue(isSuccessful) assertTrue(output.contains("\\(for architecture x86_64\\):\\s+current ar archive".toRegex())) assertTrue(output.contains("\\(for architecture arm64\\):\\s+current ar archive".toRegex())) @@ -1220,7 +1221,7 @@ class CocoaPodsIT : BaseGradleIT() { ) { val process = ProcessBuilder(command, *args).apply { directory(workingDir) - environment()["PATH"] = getPathEnv() + environment().putAll(getPathEnvs()) if (inheritIO) { inheritIO() } @@ -1405,8 +1406,7 @@ class CocoaPodsIT : BaseGradleIT() { if (cocoapodsInstallationRequired) { if (cocoapodsInstallationAllowed) { WorkaroundForXcode12_3.apply() - gem("install", "--user-install", "cocoapods", "cocoapods-generate") - cocoapodsInstallationPath = File(getGemUserInstallationPath()).resolve("bin") + gem("install", "--install-dir", cocoapodsInstallationRoot.absolutePath, "cocoapods", "cocoapods-generate") } else { fail( """ @@ -1422,65 +1422,52 @@ class CocoaPodsIT : BaseGradleIT() { @AfterClass @JvmStatic - fun uninstallPodGen() { - // Do not remove CocoaPods if we didn't install it. + fun disposeWorkarounds() { if (cocoapodsInstallationRequired && cocoapodsInstallationAllowed) { - val packagesToRemove = gem("list", "--no-versions").lineSequence().filter { - it.startsWith("cocoapods") - }.toList() - try { - gem("uninstall", "--user-install", "-x", *packagesToRemove.toTypedArray()) - } finally { - WorkaroundForXcode12_3.dispose() - } + WorkaroundForXcode12_3.dispose() } } - private val cocoapodsInstallationRequired: Boolean = !isCocoapodsInstalled() + private val cocoapodsInstallationRequired: Boolean by lazy { + !isCocoapodsInstalled() || !isPodGenInstalled() + } private val cocoapodsInstallationAllowed: Boolean = System.getProperty("installCocoapods").toBoolean() - private var cocoapodsInstallationPath: File? = null + private val cocoapodsInstallationRoot: File by lazy { createTempDir("cocoapods") } - private fun getPathEnv(): String { - val oldPath = System.getenv("PATH") - return cocoapodsInstallationPath?.let { - it.absolutePath + File.pathSeparator + oldPath - } ?: oldPath + private fun getPathEnvs(): Map { + val path = cocoapodsInstallationRoot.resolve("bin").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) } private fun isCocoapodsInstalled(): Boolean { - val installed = gem("list", "--no-versions").lines() - return "cocoapods-generate" in installed && "cocoapods" in installed + // 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("."), + environmentVariables = getPathEnvs() + ) + result.isSuccessful + } catch (e: IOException) { + false + } } - private fun getGemUserInstallationPath(): String { - val installationDirLine = gem("environment").lineSequence().first { - it.contains("USER INSTALLATION DIRECTORY:") - } - return installationDirLine.substringAfter("USER INSTALLATION DIRECTORY:").trim() + private fun isPodGenInstalled(): Boolean { + val installed = gem("list", "--no-versions").lines() + return "cocoapods-generate" in installed } private fun gem(vararg args: String): String { - val process = ProcessBuilder("gem", *args).start() - val finished = process.waitFor(30, TimeUnit.MINUTES) - val output = process.inputStream.use { it.reader().readText() } - - check(finished && process.exitValue() == 0) { - if (!finished) { - process.destroyForcibly() - } - - val argsString = args.joinToString(separator = " ") - val errors = process.errorStream.use { it.reader().readText() } - println("'gem $argsString' stdout:\n$errors") - println("'gem $argsString' stderr:\n$output") - - if (finished) { - "Process 'gem $argsString' exited with error code ${process.exitValue()}. See log for details." - } else { - "Process 'gem $argsString ' killed by timeout. See log for details" - } + val result = runProcess(listOf("gem", *args), File(".")) + check(result.isSuccessful) { + "Process 'gem ${args.joinToString(separator = " ")}' exited with error code ${result.exitCode}. See log for details." } - return output + return result.output } // Workaround the issue with Ruby paths for Xcode 12.3.