[MPP] Run pod install through env to prevent ProcessBuilder from missing
PATH modifications
^KT-60394
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 a61c143..9fc29bb 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
@@ -554,18 +554,18 @@
@DisplayName("Cinterop commonization on")
@GradleTest
fun testCinteropCommonizationOn(gradleVersion: GradleVersion) {
- testCinteropCommonizationExecutes(gradleVersion, buildArguments=arrayOf("-Pkotlin.mpp.enableCInteropCommonization=true"))
+ testCinteropCommonizationExecutes(gradleVersion, buildArguments = arrayOf("-Pkotlin.mpp.enableCInteropCommonization=true"))
}
@DisplayName("Cinterop commonization unspecified")
@GradleTest
fun testCinteropCommonizationUnspecified(gradleVersion: GradleVersion) {
- testCinteropCommonizationExecutes(gradleVersion, buildArguments=emptyArray())
+ testCinteropCommonizationExecutes(gradleVersion, buildArguments = emptyArray())
}
private fun testCinteropCommonizationExecutes(
gradleVersion: GradleVersion,
- buildArguments: Array<String>
+ buildArguments: Array<String>,
) {
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsCommonizationProjectName, gradleVersion) {
buildWithCocoapodsWrapper(":commonize", *buildArguments) {
@@ -875,18 +875,92 @@
}
}
+ private val maybeCocoaPodsIsNotInstalledError = "Possible reason: CocoaPods is not installed"
+ private val maybePodfileIsIncorrectError = "Please, check that podfile contains following lines in header"
+
+ @DisplayName("Pod install emits correct error when pod binary is not present in PATH")
+ @GradleTest
+ fun testPodInstallErrorWithoutCocoaPodsInPATH(gradleVersion: GradleVersion) {
+ val pathWithoutCocoapods = "/bin:/usr/bin"
+ nativeProjectWithCocoapodsAndIosAppPodFile(
+ gradleVersion = gradleVersion,
+ environmentVariables = EnvironmentalVariables(
+ mapOf("PATH" to pathWithoutCocoapods)
+ )
+ ) {
+ buildGradleKts.addCocoapodsBlock(
+ """
+ podfile = project.file("ios-app/Podfile")
+ """.trimIndent()
+ )
+
+ buildAndFailWithCocoapodsWrapper(
+ podInstallTaskName,
+ ) {
+ assertOutputDoesNotContain(maybePodfileIsIncorrectError)
+ assertOutputContains(maybeCocoaPodsIsNotInstalledError)
+ }
+ }
+ }
+
+ @DisplayName("Pod install emits other errors when pod install runs, but fails later")
+ @GradleTest
+ fun testOtherPodInstallErrors(gradleVersion: GradleVersion) {
+ nativeProjectWithCocoapodsAndIosAppPodFile(
+ gradleVersion = gradleVersion
+ ) {
+ buildGradleKts.addCocoapodsBlock(
+ """
+ podfile = project.file("ios-app/Podfile")
+ """.trimIndent()
+ )
+
+ projectPath.resolve("ios-app/Podfile").append(
+ """
+ raise "Dead"
+ """.trimIndent()
+ )
+
+ buildAndFailWithCocoapodsWrapper(
+ podInstallTaskName,
+ ) {
+ assertOutputContains(maybePodfileIsIncorrectError)
+ assertOutputDoesNotContain(maybeCocoaPodsIsNotInstalledError)
+ }
+ }
+ }
+
+ private fun TestProject.buildAndFailWithCocoapodsWrapper(
+ vararg buildArguments: String,
+ assertions: BuildResult.() -> Unit = {},
+ ) = buildWithCocoapodsWrapperUsing { buildOptions ->
+ buildAndFail(
+ *buildArguments,
+ buildOptions = buildOptions,
+ assertions = assertions,
+ )
+ }
+
private fun TestProject.buildWithCocoapodsWrapper(
vararg buildArguments: String,
assertions: BuildResult.() -> Unit = {},
+ ) = buildWithCocoapodsWrapperUsing { buildOptions ->
+ build(
+ *buildArguments,
+ buildOptions = buildOptions,
+ assertions = assertions,
+ )
+ }
+
+ private fun TestProject.buildWithCocoapodsWrapperUsing(
+ builder: TestProject.(BuildOptions) -> Unit,
) {
val buildOptions = this.buildOptions.copy(
nativeOptions = this.buildOptions.nativeOptions.copy(
cocoapodsGenerateWrapper = true
)
)
- build(*buildArguments, buildOptions = buildOptions) {
- assertions()
- }
+ builder(buildOptions)
}
private fun TestProject.addPodToPodfile(iosAppLocation: String, pod: String) {
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 97e2e39..b5f41b7 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
@@ -256,7 +256,7 @@
build("$taskPrefix:$DUMMY_FRAMEWORK_TASK_NAME", buildOptions = buildOptions)
runProcess(
- cmd = listOf("pod", "install"),
+ cmd = listOf("env", "pod", "install"),
environmentVariables = environmentVariables.environmentalVariables,
workingDir = iosAppPath.toFile(),
)
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 ddd5356..adeb1e4 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
@@ -196,13 +196,14 @@
projectName: String = templateProjectName,
gradleVersion: GradleVersion,
buildOptions: BuildOptions = this.defaultBuildOptions,
+ environmentVariables: EnvironmentalVariables = EnvironmentalVariables(cocoaPodsEnvironmentVariables()),
projectBlock: TestProject.() -> Unit = {},
) {
nativeProject(
projectName,
gradleVersion,
buildOptions = buildOptions,
- environmentVariables = EnvironmentalVariables(cocoaPodsEnvironmentVariables())
+ environmentVariables = environmentVariables,
) {
preparePodfile("ios-app", ImportMode.FRAMEWORKS)
projectBlock()
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt
index d661038..78301f7 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt
@@ -42,14 +42,14 @@
@TaskAction
open fun doPodInstall() {
- val podInstallCommand = listOf("pod", "install")
+ // env is used here to work around the JVM PATH caching when spawning a child process with custom environment, i.e. LC_ALL
+ // The caching causes the ProcessBuilder to ignore changes in the PATH that may occur on incremental runs of the Gradle daemon
+ // KT-60394
+ val podInstallCommand = listOf("env", "pod", "install")
runCommand(podInstallCommand,
logger,
- errorHandler = ::handleError,
- exceptionHandler = { e: IOException ->
- CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
- },
+ errorHandler = { retCode, output, process -> sharedHandleError(podInstallCommand, retCode, output, process) },
processConfiguration = {
directory(workingDir.get())
// CocoaPods requires to be run with Unicode external encoding
@@ -63,17 +63,14 @@
}
}
- abstract fun handleError(retCode: Int, error: String, process: Process): String?
-}
-
-private object CocoapodsErrorHandlingUtil {
- fun handle(e: IOException, command: List<String>) {
- if (e.message?.contains("No such file or directory") == true) {
- val message = """
- |'${command.take(2).joinToString(" ")}' command failed with an exception:
- | ${e.message}
+ private fun sharedHandleError(podInstallCommand: List<String>, retCode: Int, error: String, process: Process): String? {
+ return if (error.contains("No such file or directory")) {
+ val command = podInstallCommand.joinToString(" ")
+ """
+ |'$command' command failed with an exception:
+ | $error
|
- | Full command: ${command.joinToString(" ")}
+ | Full command: $command
|
| Possible reason: CocoaPods is not installed
| Please check that CocoaPods v1.10 or above is installed.
@@ -83,10 +80,10 @@
| To install CocoaPods execute 'sudo gem install cocoapods'
|
""".trimMargin()
- throw IllegalStateException(message)
} else {
- throw e
+ handleError(retCode, error, process)
}
}
-}
+ abstract fun handleError(retCode: Int, error: String, process: Process): String?
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ProcessUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ProcessUtils.kt
index 478a9e7..03cef4b 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ProcessUtils.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ProcessUtils.kt
@@ -13,22 +13,11 @@
command: List<String>,
logger: Logger? = null,
errorHandler: ((retCode: Int, output: String, process: Process) -> String?)? = null,
- exceptionHandler: ((ex: IOException) -> Unit)? = null,
processConfiguration: ProcessBuilder.() -> Unit = { }
): String {
- var process: Process? = null
- try {
- process = ProcessBuilder(command)
- .apply {
- this.processConfiguration()
- }.start()
- } catch (e: IOException) {
- if (exceptionHandler != null) exceptionHandler(e) else throw e
- }
-
- if (process == null) {
- throw IllegalStateException("Failed to run command ${command.joinToString(" ")}")
- }
+ val process = ProcessBuilder(command).apply {
+ this.processConfiguration()
+ }.start()
var inputText = ""
var errorText = ""