[MPP] Run pod install through env to prevent ProcessBuilder from missing
PATH modifications ^KT-60394
This commit is contained in:
committed by
Space Team
parent
78fa93d75c
commit
6edfda0204
+80
-6
@@ -554,18 +554,18 @@ class CocoaPodsIT : KGPBaseTest() {
|
|||||||
@DisplayName("Cinterop commonization on")
|
@DisplayName("Cinterop commonization on")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testCinteropCommonizationOn(gradleVersion: GradleVersion) {
|
fun testCinteropCommonizationOn(gradleVersion: GradleVersion) {
|
||||||
testCinteropCommonizationExecutes(gradleVersion, buildArguments=arrayOf("-Pkotlin.mpp.enableCInteropCommonization=true"))
|
testCinteropCommonizationExecutes(gradleVersion, buildArguments = arrayOf("-Pkotlin.mpp.enableCInteropCommonization=true"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("Cinterop commonization unspecified")
|
@DisplayName("Cinterop commonization unspecified")
|
||||||
@GradleTest
|
@GradleTest
|
||||||
fun testCinteropCommonizationUnspecified(gradleVersion: GradleVersion) {
|
fun testCinteropCommonizationUnspecified(gradleVersion: GradleVersion) {
|
||||||
testCinteropCommonizationExecutes(gradleVersion, buildArguments=emptyArray())
|
testCinteropCommonizationExecutes(gradleVersion, buildArguments = emptyArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testCinteropCommonizationExecutes(
|
private fun testCinteropCommonizationExecutes(
|
||||||
gradleVersion: GradleVersion,
|
gradleVersion: GradleVersion,
|
||||||
buildArguments: Array<String>
|
buildArguments: Array<String>,
|
||||||
) {
|
) {
|
||||||
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsCommonizationProjectName, gradleVersion) {
|
nativeProjectWithCocoapodsAndIosAppPodFile(cocoapodsCommonizationProjectName, gradleVersion) {
|
||||||
buildWithCocoapodsWrapper(":commonize", *buildArguments) {
|
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(
|
private fun TestProject.buildWithCocoapodsWrapper(
|
||||||
vararg buildArguments: String,
|
vararg buildArguments: String,
|
||||||
assertions: BuildResult.() -> Unit = {},
|
assertions: BuildResult.() -> Unit = {},
|
||||||
|
) = buildWithCocoapodsWrapperUsing { buildOptions ->
|
||||||
|
build(
|
||||||
|
*buildArguments,
|
||||||
|
buildOptions = buildOptions,
|
||||||
|
assertions = assertions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TestProject.buildWithCocoapodsWrapperUsing(
|
||||||
|
builder: TestProject.(BuildOptions) -> Unit,
|
||||||
) {
|
) {
|
||||||
val buildOptions = this.buildOptions.copy(
|
val buildOptions = this.buildOptions.copy(
|
||||||
nativeOptions = this.buildOptions.nativeOptions.copy(
|
nativeOptions = this.buildOptions.nativeOptions.copy(
|
||||||
cocoapodsGenerateWrapper = true
|
cocoapodsGenerateWrapper = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
build(*buildArguments, buildOptions = buildOptions) {
|
builder(buildOptions)
|
||||||
assertions()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestProject.addPodToPodfile(iosAppLocation: String, pod: String) {
|
private fun TestProject.addPodToPodfile(iosAppLocation: String, pod: String) {
|
||||||
|
|||||||
+1
-1
@@ -256,7 +256,7 @@ private fun TestProject.manualPodInstall(taskPrefix: String, iosAppPath: Path) {
|
|||||||
build("$taskPrefix:$DUMMY_FRAMEWORK_TASK_NAME", buildOptions = buildOptions)
|
build("$taskPrefix:$DUMMY_FRAMEWORK_TASK_NAME", buildOptions = buildOptions)
|
||||||
|
|
||||||
runProcess(
|
runProcess(
|
||||||
cmd = listOf("pod", "install"),
|
cmd = listOf("env", "pod", "install"),
|
||||||
environmentVariables = environmentVariables.environmentalVariables,
|
environmentVariables = environmentVariables.environmentalVariables,
|
||||||
workingDir = iosAppPath.toFile(),
|
workingDir = iosAppPath.toFile(),
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-1
@@ -196,13 +196,14 @@ fun KGPBaseTest.nativeProjectWithCocoapodsAndIosAppPodFile(
|
|||||||
projectName: String = templateProjectName,
|
projectName: String = templateProjectName,
|
||||||
gradleVersion: GradleVersion,
|
gradleVersion: GradleVersion,
|
||||||
buildOptions: BuildOptions = this.defaultBuildOptions,
|
buildOptions: BuildOptions = this.defaultBuildOptions,
|
||||||
|
environmentVariables: EnvironmentalVariables = EnvironmentalVariables(cocoaPodsEnvironmentVariables()),
|
||||||
projectBlock: TestProject.() -> Unit = {},
|
projectBlock: TestProject.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
nativeProject(
|
nativeProject(
|
||||||
projectName,
|
projectName,
|
||||||
gradleVersion,
|
gradleVersion,
|
||||||
buildOptions = buildOptions,
|
buildOptions = buildOptions,
|
||||||
environmentVariables = EnvironmentalVariables(cocoaPodsEnvironmentVariables())
|
environmentVariables = environmentVariables,
|
||||||
) {
|
) {
|
||||||
preparePodfile("ios-app", ImportMode.FRAMEWORKS)
|
preparePodfile("ios-app", ImportMode.FRAMEWORKS)
|
||||||
projectBlock()
|
projectBlock()
|
||||||
|
|||||||
+14
-17
@@ -42,14 +42,14 @@ abstract class AbstractPodInstallTask : CocoapodsTask() {
|
|||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
open fun doPodInstall() {
|
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,
|
runCommand(podInstallCommand,
|
||||||
logger,
|
logger,
|
||||||
errorHandler = ::handleError,
|
errorHandler = { retCode, output, process -> sharedHandleError(podInstallCommand, retCode, output, process) },
|
||||||
exceptionHandler = { e: IOException ->
|
|
||||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
|
||||||
},
|
|
||||||
processConfiguration = {
|
processConfiguration = {
|
||||||
directory(workingDir.get())
|
directory(workingDir.get())
|
||||||
// CocoaPods requires to be run with Unicode external encoding
|
// 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 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(" ")
|
||||||
private object CocoapodsErrorHandlingUtil {
|
"""
|
||||||
fun handle(e: IOException, command: List<String>) {
|
|'$command' command failed with an exception:
|
||||||
if (e.message?.contains("No such file or directory") == true) {
|
| $error
|
||||||
val message = """
|
|
||||||
|'${command.take(2).joinToString(" ")}' command failed with an exception:
|
|
||||||
| ${e.message}
|
|
||||||
|
|
|
|
||||||
| Full command: ${command.joinToString(" ")}
|
| Full command: $command
|
||||||
|
|
|
|
||||||
| Possible reason: CocoaPods is not installed
|
| Possible reason: CocoaPods is not installed
|
||||||
| Please check that CocoaPods v1.10 or above is 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'
|
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||||
|
|
|
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
throw IllegalStateException(message)
|
|
||||||
} else {
|
} else {
|
||||||
throw e
|
handleError(retCode, error, process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract fun handleError(retCode: Int, error: String, process: Process): String?
|
||||||
}
|
}
|
||||||
+3
-14
@@ -13,22 +13,11 @@ internal fun runCommand(
|
|||||||
command: List<String>,
|
command: List<String>,
|
||||||
logger: Logger? = null,
|
logger: Logger? = null,
|
||||||
errorHandler: ((retCode: Int, output: String, process: Process) -> String?)? = null,
|
errorHandler: ((retCode: Int, output: String, process: Process) -> String?)? = null,
|
||||||
exceptionHandler: ((ex: IOException) -> Unit)? = null,
|
|
||||||
processConfiguration: ProcessBuilder.() -> Unit = { }
|
processConfiguration: ProcessBuilder.() -> Unit = { }
|
||||||
): String {
|
): String {
|
||||||
var process: Process? = null
|
val process = ProcessBuilder(command).apply {
|
||||||
try {
|
this.processConfiguration()
|
||||||
process = ProcessBuilder(command)
|
}.start()
|
||||||
.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(" ")}")
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputText = ""
|
var inputText = ""
|
||||||
var errorText = ""
|
var errorText = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user