[MPP] Run pod install through env to prevent ProcessBuilder from missing

PATH modifications

^KT-60394
This commit is contained in:
Timofey Solonin
2023-07-13 14:51:13 +02:00
committed by Space Team
parent 78fa93d75c
commit 6edfda0204
5 changed files with 101 additions and 40 deletions
@@ -554,18 +554,18 @@ class CocoaPodsIT : KGPBaseTest() {
@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 @@ class CocoaPodsIT : KGPBaseTest() {
}
}
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) {
@@ -256,7 +256,7 @@ private fun TestProject.manualPodInstall(taskPrefix: String, iosAppPath: Path) {
build("$taskPrefix:$DUMMY_FRAMEWORK_TASK_NAME", buildOptions = buildOptions)
runProcess(
cmd = listOf("pod", "install"),
cmd = listOf("env", "pod", "install"),
environmentVariables = environmentVariables.environmentalVariables,
workingDir = iosAppPath.toFile(),
)
@@ -196,13 +196,14 @@ fun KGPBaseTest.nativeProjectWithCocoapodsAndIosAppPodFile(
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()
@@ -42,14 +42,14 @@ abstract class AbstractPodInstallTask : CocoapodsTask() {
@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 class AbstractPodInstallTask : CocoapodsTask() {
}
}
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 @@ private object CocoapodsErrorHandlingUtil {
| 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?
}
@@ -13,22 +13,11 @@ internal fun runCommand(
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 = ""