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 -> pods.all { pod ->
binary.linkerOpts("-framework", pod.moduleName) 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 -> binary.linkTaskProvider.configure { task ->
val podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod) val podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod)
@@ -299,13 +299,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
// to avoid showing it in the `tasks` output. // 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 -> kotlinExtension.supportedTargets().all { target ->
target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.create(pod.moduleName) { interop -> 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.description = "Invokes `pod install` call within Podfile location directory"
it.podfile.set(cocoapodsExtension.podfile) it.podfile.set(cocoapodsExtension.podfile)
it.frameworkName = project.provider { cocoapodsExtension.frameworkNameInternal } it.frameworkName = project.provider { cocoapodsExtension.frameworkNameInternal }
it.onlyIf { isAvailableToProduceSynthetic }
it.dependsOn(podspecTaskProvider) it.dependsOn(podspecTaskProvider)
} }
} }
@@ -449,7 +441,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.family = family it.family = family
it.pods.set(cocoapodsExtension.pods) it.pods.set(cocoapodsExtension.pods)
it.dependsOn(downloadPods) it.dependsOn(downloadPods)
it.onlyIf { isAvailableToProduceSynthetic }
} }
} }
} }
@@ -487,7 +478,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.podsXcodeProjDir = podGenTaskProvider.map { podGen -> podGen.podsXcodeProjDir.get() } it.podsXcodeProjDir = podGenTaskProvider.map { podGen -> podGen.podsXcodeProjDir.get() }
it.frameworkName = project.provider { cocoapodsExtension.frameworkNameInternal } it.frameworkName = project.provider { cocoapodsExtension.frameworkNameInternal }
it.dependsOn(podGenTaskProvider) it.dependsOn(podGenTaskProvider)
it.onlyIf { isAvailableToProduceSynthetic }
} }
} }
} }
@@ -528,7 +518,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
it.pod = project.provider { pod } it.pod = project.provider { pod }
it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() } it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() }
it.buildSettingsFile = podSetupBuildTaskProvider.map { task -> task.buildSettingsFile.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.group = TASK_GROUP
it.description = "Called on Gradle sync, depends on Cinterop tasks for every used pod" it.description = "Called on Gradle sync, depends on Cinterop tasks for every used pod"
it.dependsOn(podInstallTaskProvider) it.dependsOn(podInstallTaskProvider)
it.onlyIf { isAvailableToProduceSynthetic }
kotlinExtension.supportedTargets().all { target -> kotlinExtension.supportedTargets().all { target ->
target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.all { interop -> 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.FileTree
import org.gradle.api.file.RelativePath import org.gradle.api.file.RelativePath
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.api.Project
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.Optional 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.gradle.tasks.PodspecTask.Companion.retrieveSpecRepos
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.Family
import java.io.File import java.io.File
import java.io.IOException
import java.io.Reader import java.io.Reader
import java.net.URI import java.net.URI
import java.util.* import java.util.*
@@ -64,41 +66,23 @@ open class PodInstallTask : DefaultTask() {
project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") } project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") }
} }
@TaskAction @TaskAction
fun doPodInstall() { fun doPodInstall() {
podfile.orNull?.parentFile?.also { podfileDir -> podfile.orNull?.parentFile?.also { podfileDir ->
val podInstallProcess = ProcessBuilder("pod", "install").apply { val podInstallCommand = listOf("pod", "install")
directory(podfileDir)
}.start()
val podInstallRetCode = podInstallProcess.waitFor()
val podInstallOutput = podInstallProcess.inputStream.use { it.reader().readText() }
check(podInstallRetCode == 0) { runCommand(podInstallCommand,
val specReposMessages = retrieveSpecRepos(project)?.let { MissingSpecReposMessage(it).missingMessage } project.logger,
val cocoapodsMessages = retrievePods(project)?.map { MissingCocoapodsMessage(it, project).missingMessage } 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) { with(podsXcodeProjDirProvider) {
check(this != null && get().exists() && get().isDirectory) { check(this != null && get().exists() && get().isDirectory) {
"The directory 'Pods/Pods.xcodeproj' was not created as a result of the `pod install` call." "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() { abstract class DownloadCocoapodsTask : DefaultTask() {
@get:Input @get:Input
internal lateinit var podName: Provider<String> internal lateinit var podName: Provider<String>
@@ -303,14 +282,23 @@ open class PodDownloadGitTask : DownloadCocoapodsTask() {
private fun runCommand( private fun runCommand(
command: List<String>, command: List<String>,
logger: Logger, logger: Logger,
extendedErrorDiagnostic: ExtendedErrorDiagnostic? = null, errorHandler: ((retCode: Int, output: String, process: Process) -> String?)? = null,
errorHandler: ((retCode: Int, process: Process) -> Unit)? = null, exceptionHandler: ((ex: IOException) -> Unit)? = null,
processConfiguration: ProcessBuilder.() -> Unit = { } processConfiguration: ProcessBuilder.() -> Unit = { }
): String { ): String {
val process = ProcessBuilder(command) var process: Process? = null
.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 = ""
@@ -340,12 +328,14 @@ private fun runCommand(
) )
check(retCode == 0) { check(retCode == 0) {
errorHandler?.invoke(retCode, process) errorHandler?.invoke(retCode, inputText, process)
?: """ ?: """
|Executing of '${command.joinToString(" ")}' failed with code $retCode and message: |Executing of '${command.joinToString(" ")}' failed with code $retCode and message:
| |
|$inputText
|
|$errorText |$errorText
|${extendedErrorDiagnostic?.takeIf { it.isAppropriateOutput(inputText) }?.message ?: ""} |
""".trimMargin() """.trimMargin()
} }
@@ -406,23 +396,18 @@ open class PodGenTask : DefaultTask() {
podspec.get().absolutePath podspec.get().absolutePath
) )
val podGenDiagnostic = object : ExtendedErrorDiagnostic { runCommand(
override fun isAppropriateOutput(output: String): Boolean = podGenProcessArgs,
output.contains("deployment target") || output.contains("requested platforms: [\"${family.platformLiteral}\"]") project.logger,
exceptionHandler = { e: IOException ->
override val message: String CocoapodsErrorHandlingUtil.handle(e, podGenProcessArgs)
get() = },
""" errorHandler = { retCode, output, _ ->
|Tip: try to configure deployment_target for ALL targets as follows: CocoapodsErrorHandlingUtil.handlePodGenError(retCode, output, family)
|cocoapods { },
| ... processConfiguration = {
| ${family.name.toLowerCase()}.deploymentTarget = "..." directory(syntheticDir)
| ... })
|}
""".trimMargin()
}
runCommand(podGenProcessArgs, project.logger, podGenDiagnostic) { directory(syntheticDir) }
val podsXcprojFile = podsXcodeProjDir.get() val podsXcprojFile = podsXcodeProjDir.get()
check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) { check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) {
@@ -596,3 +581,89 @@ data class PodBuildSettingsProperties(
getProperty(propertyName) 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
}
}