Cocoapods plugin: error reporting in case CocoaPods is not installed on users machine

#KT-47362
This commit is contained in:
Viacheslav Kormushkin
2021-08-06 12:19:55 +03:00
committed by Space
parent 1b81018b69
commit d670743a2f
3 changed files with 131 additions and 79 deletions
@@ -254,13 +254,6 @@ open class CocoapodsExtension(private val project: Project) {
pods.all { pod ->
binary.linkerOpts("-framework", pod.moduleName)
check(KotlinCocoapodsPlugin.isAvailableToProduceSynthetic) {
"""
Dependency on pods requires cocoapods-generate plugin to be installed.
Please install it by executing 'gem install cocoapods-generate' in terminal.
""".trimMargin()
}
binary.linkTaskProvider.configure { task ->
val podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod)
@@ -299,13 +299,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
// to avoid showing it in the `tasks` output.
}
check(isAvailableToProduceSynthetic) {
"""
Dependency on pods requires cocoapods-generate plugin to be installed.
Please install it by executing 'gem install cocoapods-generate' in terminal.
""".trimMargin()
}
kotlinExtension.supportedTargets().all { target ->
target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.create(pod.moduleName) { interop ->
@@ -395,7 +388,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.description = "Invokes `pod install` call within Podfile location directory"
it.podfile.set(cocoapodsExtension.podfile)
it.frameworkName = project.provider { cocoapodsExtension.frameworkNameInternal }
it.onlyIf { isAvailableToProduceSynthetic }
it.dependsOn(podspecTaskProvider)
}
}
@@ -449,7 +441,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.family = family
it.pods.set(cocoapodsExtension.pods)
it.dependsOn(downloadPods)
it.onlyIf { isAvailableToProduceSynthetic }
}
}
}
@@ -487,7 +478,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.podsXcodeProjDir = podGenTaskProvider.map { podGen -> podGen.podsXcodeProjDir.get() }
it.frameworkName = project.provider { cocoapodsExtension.frameworkNameInternal }
it.dependsOn(podGenTaskProvider)
it.onlyIf { isAvailableToProduceSynthetic }
}
}
}
@@ -528,7 +518,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.pod = project.provider { pod }
it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() }
it.buildSettingsFile = podSetupBuildTaskProvider.map { task -> task.buildSettingsFile.get() }
it.onlyIf { isAvailableToProduceSynthetic }
}
}
}
@@ -543,7 +532,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.group = TASK_GROUP
it.description = "Called on Gradle sync, depends on Cinterop tasks for every used pod"
it.dependsOn(podInstallTaskProvider)
it.onlyIf { isAvailableToProduceSynthetic }
kotlinExtension.supportedTargets().all { target ->
target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.all { interop ->
@@ -11,6 +11,7 @@ import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.file.RelativePath
import org.gradle.api.logging.Logger
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.Optional
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.gradle.tasks.PodspecTask.Companion.retrievePods
import org.jetbrains.kotlin.gradle.tasks.PodspecTask.Companion.retrieveSpecRepos
import org.jetbrains.kotlin.konan.target.Family
import java.io.File
import java.io.IOException
import java.io.Reader
import java.net.URI
import java.util.*
@@ -64,41 +66,23 @@ open class PodInstallTask : DefaultTask() {
project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") }
}
@TaskAction
fun doPodInstall() {
podfile.orNull?.parentFile?.also { podfileDir ->
val podInstallProcess = ProcessBuilder("pod", "install").apply {
directory(podfileDir)
}.start()
val podInstallRetCode = podInstallProcess.waitFor()
val podInstallOutput = podInstallProcess.inputStream.use { it.reader().readText() }
val podInstallCommand = listOf("pod", "install")
check(podInstallRetCode == 0) {
val specReposMessages = retrieveSpecRepos(project)?.let { MissingSpecReposMessage(it).missingMessage }
val cocoapodsMessages = retrievePods(project)?.map { MissingCocoapodsMessage(it, project).missingMessage }
runCommand(podInstallCommand,
project.logger,
errorHandler = { returnCode, output, _ ->
CocoapodsErrorHandlingUtil.handlePodInstallError(returnCode, output, project, frameworkName.get())
},
exceptionHandler = { e: IOException ->
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
},
processConfiguration = {
directory(podfileDir)
})
listOfNotNull(
"Executing of 'pod install' failed with code $podInstallRetCode.",
"Error message:",
podInstallOutput,
specReposMessages?.let {
"""
|Please, check that file "${podfile.get().path}" contains following lines in header:
|$it
|
""".trimMargin()
},
cocoapodsMessages?.let {
"""
|Please, check that each target depended on ${frameworkName.get()} contains following dependencies:
|${it.joinToString("\n")}
|
""".trimMargin()
}
).joinToString("\n")
}
with(podsXcodeProjDirProvider) {
check(this != null && get().exists() && get().isDirectory) {
"The directory 'Pods/Pods.xcodeproj' was not created as a result of the `pod install` call."
@@ -108,11 +92,6 @@ open class PodInstallTask : DefaultTask() {
}
}
private interface ExtendedErrorDiagnostic {
fun isAppropriateOutput(output: String): Boolean
val message: String
}
abstract class DownloadCocoapodsTask : DefaultTask() {
@get:Input
internal lateinit var podName: Provider<String>
@@ -303,14 +282,23 @@ open class PodDownloadGitTask : DownloadCocoapodsTask() {
private fun runCommand(
command: List<String>,
logger: Logger,
extendedErrorDiagnostic: ExtendedErrorDiagnostic? = null,
errorHandler: ((retCode: Int, process: Process) -> Unit)? = null,
errorHandler: ((retCode: Int, output: String, process: Process) -> String?)? = null,
exceptionHandler: ((ex: IOException) -> Unit)? = null,
processConfiguration: ProcessBuilder.() -> Unit = { }
): String {
val process = ProcessBuilder(command)
.apply {
this.processConfiguration()
}.start()
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(" ")}")
}
var inputText = ""
var errorText = ""
@@ -340,12 +328,14 @@ private fun runCommand(
)
check(retCode == 0) {
errorHandler?.invoke(retCode, process)
errorHandler?.invoke(retCode, inputText, process)
?: """
|Executing of '${command.joinToString(" ")}' failed with code $retCode and message:
|
|$inputText
|
|$errorText
|${extendedErrorDiagnostic?.takeIf { it.isAppropriateOutput(inputText) }?.message ?: ""}
|
""".trimMargin()
}
@@ -406,23 +396,18 @@ open class PodGenTask : DefaultTask() {
podspec.get().absolutePath
)
val podGenDiagnostic = object : ExtendedErrorDiagnostic {
override fun isAppropriateOutput(output: String): Boolean =
output.contains("deployment target") || output.contains("requested platforms: [\"${family.platformLiteral}\"]")
override val message: String
get() =
"""
|Tip: try to configure deployment_target for ALL targets as follows:
|cocoapods {
| ...
| ${family.name.toLowerCase()}.deploymentTarget = "..."
| ...
|}
""".trimMargin()
}
runCommand(podGenProcessArgs, project.logger, podGenDiagnostic) { directory(syntheticDir) }
runCommand(
podGenProcessArgs,
project.logger,
exceptionHandler = { e: IOException ->
CocoapodsErrorHandlingUtil.handle(e, podGenProcessArgs)
},
errorHandler = { retCode, output, _ ->
CocoapodsErrorHandlingUtil.handlePodGenError(retCode, output, family)
},
processConfiguration = {
directory(syntheticDir)
})
val podsXcprojFile = podsXcodeProjDir.get()
check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) {
@@ -596,3 +581,89 @@ data class PodBuildSettingsProperties(
getProperty(propertyName)
}
}
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}
|
| Possible reason: CocoaPods is not installed
| Please check that CocoaPods v1.10 or above and cocoapods-generate plugin are installed.
|
| To check CocoaPods version type 'pod --version' in the terminal
|
| To install CocoaPods execute 'sudo gem install cocoapods'
| To install cocoapod-generate execute 'sudo gem install cocoapods-generate'
|
""".trimMargin()
throw IllegalStateException(message)
} else {
throw e
}
}
fun handlePodInstallError(retCode: Int, error: String, project: Project, frameworkName: String): String {
val specReposMessages = retrieveSpecRepos(project)?.let { MissingSpecReposMessage(it).missingMessage }
val cocoapodsMessages = retrievePods(project)?.map { MissingCocoapodsMessage(it, project).missingMessage }
return listOfNotNull(
"'pod install' command failed with code $retCode.",
"Error message:",
error,
specReposMessages?.let {
"""
| Please, check that podfile contains following lines in header:
| $it
|
""".trimMargin()
},
cocoapodsMessages?.let {
"""
| Please, check that each target depended on $frameworkName contains following dependencies:
| ${it.joinToString("\n")}
|
""".trimMargin()
}
).joinToString("\n")
}
fun handlePodGenError(retCode: Int, error: String, family: Family): String? {
var message = """
|'pod gen' command failed with return code: $retCode
|
| Error: ${error.lines().filter { it.contains("[!]") }?.joinToString("\n")}
""".trimMargin()
if (error.contains("Unknown command: `gen`")) {
message += """
|
| Possible reason: cocoapod-generate is not installed
| To install cocoapod-generate execute 'sudo gem install cocoapods-generate' in the terminal
|
""".trimMargin()
return message
} else if (
error.contains("deployment target") ||
error.contains("requested platforms:") ||
error.contains("no platform was specified")
) {
message += """
|
| Possible reason: ${family.name.toLowerCase()} deployment target is not configured
| Configure deployment_target for ALL targets as follows:
| cocoapods {
| ...
| ${family.name.toLowerCase()}.deploymentTarget = "..."
| ...
| }
|
""".trimMargin()
return message
}
return null
}
}