Separate podgen & podinstall tasks

^KT-54161 Verification Pending
This commit is contained in:
Artem Daugel-Dauge
2023-01-10 13:12:13 +01:00
committed by Space Team
parent f144f8b6d3
commit 8a34d1f430
6 changed files with 253 additions and 246 deletions
@@ -70,9 +70,10 @@ class CocoaPodsIT : BaseGradleIT() {
private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking" private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking"
private val defaultPodName = "AFNetworking" private val defaultPodName = "AFNetworking"
private val defaultTarget = "IOS" private val defaultTarget = "IOS"
private val defaultFamily = "IOS" private val defaultFamily = "ios"
private val defaultSDK = "iphonesimulator" private val defaultSDK = "iphonesimulator"
private val defaultPodGenTaskName = podGenFullTaskName() private val defaultPodGenTaskName = podGenFullTaskName()
private val defaultPodInstallSyntheticTaskName = ":podInstallSyntheticIos"
private val defaultBuildTaskName = podBuildFullTaskName() private val defaultBuildTaskName = podBuildFullTaskName()
private val defaultSetupBuildTaskName = podSetupBuildFullTaskName() private val defaultSetupBuildTaskName = podSetupBuildFullTaskName()
private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget
@@ -245,7 +246,7 @@ class CocoaPodsIT : BaseGradleIT() {
} }
@Test @Test
fun testSyntheticProjectPodspecGeneration() { fun testSyntheticProjectPodfileGeneration() {
val gradleProject = transformProjectWithPluginsDsl(cocoapodsSingleKtPod, gradleVersion) val gradleProject = transformProjectWithPluginsDsl(cocoapodsSingleKtPod, gradleVersion)
gradleProject.gradleBuildScript().appendToCocoapodsBlock(""" gradleProject.gradleBuildScript().appendToCocoapodsBlock("""
ios.deploymentTarget = "14.1" ios.deploymentTarget = "14.1"
@@ -257,9 +258,11 @@ class CocoaPodsIT : BaseGradleIT() {
} }
} }
""".trimIndent()) """.trimIndent())
gradleProject.build("podGenIOS", "-Pkotlin.native.cocoapods.generate.wrapper=true") { gradleProject.build("podInstallSyntheticIos", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
assertSuccessful() assertSuccessful()
val podfileText = gradleProject.projectDir.resolve("build/cocoapods/synthetic/IOS/Podfile").readText().trim() assertTasksExecuted(":podGenIos")
val podfileText = gradleProject.projectDir.resolve("build/cocoapods/synthetic/ios/Podfile").readText().trim()
assertTrue(podfileText.contains("platform :ios, '14.1'")) assertTrue(podfileText.contains("platform :ios, '14.1'"))
assertTrue(podfileText.contains("pod 'SSZipArchive'")) assertTrue(podfileText.contains("pod 'SSZipArchive'"))
assertTrue(podfileText.contains("pod 'AFNetworking', '~> 4.0.1'")) assertTrue(podfileText.contains("pod 'AFNetworking', '~> 4.0.1'"))
@@ -270,6 +273,27 @@ class CocoaPodsIT : BaseGradleIT() {
} }
} }
@Test
fun testSyntheticProjectPodfilePostprocessing() {
project.gradleBuildScript().apply {
appendToCocoapodsBlock("""pod("AWSMobileClient", version = "2.29.1")""")
appendText("""
tasks.withType<org.jetbrains.kotlin.gradle.targets.native.tasks.PodGenTask>().configureEach {
doLast {
podfile.get().appendText("ENV['SWIFT_VERSION'] = '5'")
}
}
""".trimIndent())
}
project.build("podInstallSyntheticIos", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
assertSuccessful()
assertFileContains(path = "build/cocoapods/synthetic/ios/Podfile", "ENV['SWIFT_VERSION'] = '5'")
}
}
@Test @Test
fun testPodDownloadGitNoTagNorCommit() { fun testPodDownloadGitNoTagNorCommit() {
doTestGit() doTestGit()
@@ -378,9 +402,10 @@ class CocoaPodsIT : BaseGradleIT() {
val tasks = listOf( val tasks = listOf(
podspecTaskName, podspecTaskName,
defaultPodGenTaskName, defaultPodGenTaskName,
defaultPodInstallSyntheticTaskName,
defaultSetupBuildTaskName, defaultSetupBuildTaskName,
defaultBuildTaskName, defaultBuildTaskName,
defaultCinteropTaskName defaultCinteropTaskName,
) )
with(project.gradleBuildScript()) { with(project.gradleBuildScript()) {
addPod(defaultPodName, produceGitBlock(defaultPodRepo)) addPod(defaultPodName, produceGitBlock(defaultPodRepo))
@@ -416,21 +441,21 @@ class CocoaPodsIT : BaseGradleIT() {
} }
@Test @Test
fun testPodGenInvalidatesUTD() { fun testPodInstallInvalidatesUTD() {
with(project.gradleBuildScript()) { with(project.gradleBuildScript()) {
addPod("AFNetworking") addPod("AFNetworking")
} }
hooks.addHook { hooks.addHook {
assertTasksExecuted(defaultPodGenTaskName) assertTasksExecuted(defaultPodInstallSyntheticTaskName)
assertTrue { fileInWorkingDir("build/cocoapods/synthetic/IOS/Pods/AFNetworking").deleteRecursively() } assertTrue { fileInWorkingDir("build/cocoapods/synthetic/ios/Pods/AFNetworking").deleteRecursively() }
} }
project.testSynthetic(defaultPodGenTaskName) project.testSynthetic(defaultPodInstallSyntheticTaskName)
hooks.rewriteHooks { hooks.rewriteHooks {
assertTasksExecuted(defaultPodGenTaskName) assertTasksExecuted(defaultPodInstallSyntheticTaskName)
} }
project.testSynthetic(defaultPodGenTaskName) project.testSynthetic(defaultPodInstallSyntheticTaskName)
} }
@Test @Test
@@ -499,7 +524,7 @@ class CocoaPodsIT : BaseGradleIT() {
val anotherTarget = "MacosX64" val anotherTarget = "MacosX64"
val anotherSdk = "macosx" val anotherSdk = "macosx"
val anotherFamily = "OSX" val anotherFamily = "macos"
with(project.gradleBuildScript()) { with(project.gradleBuildScript()) {
appendToKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()") appendToKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()")
} }
@@ -10,22 +10,17 @@ import org.gradle.api.Action
import org.gradle.api.Named import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectSet import org.gradle.api.NamedDomainObjectSet
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBinary
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.tasks.addArg
import org.jetbrains.kotlin.gradle.tasks.addArgs
import org.jetbrains.kotlin.gradle.utils.relativeToRoot
import org.jetbrains.kotlin.konan.target.HostManager
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import javax.inject.Inject import javax.inject.Inject
@Suppress("unused") // Public API
abstract class CocoapodsExtension @Inject constructor(private val project: Project) { abstract class CocoapodsExtension @Inject constructor(private val project: Project) {
/** /**
* Configure version of the pod * Configure version of the pod
@@ -62,7 +62,7 @@ internal class CocoapodsBuildDirs(val project: Project) {
val synthetic: File val synthetic: File
get() = root.resolve("synthetic") get() = root.resolve("synthetic")
fun synthetic(family: Family) = synthetic.resolve(family.name) fun synthetic(family: Family) = synthetic.resolve(family.platformLiteral)
val publish: File = root.resolve("publish") val publish: File = root.resolve("publish")
@@ -72,11 +72,20 @@ internal class CocoapodsBuildDirs(val project: Project) {
internal fun String.asValidFrameworkName() = replace('-', '_') internal fun String.asValidFrameworkName() = replace('-', '_')
internal val Family.platformLiteral: String
get() = when (this) {
Family.OSX -> "macos"
Family.IOS -> "ios"
Family.TVOS -> "tvos"
Family.WATCHOS -> "watchos"
else -> throw IllegalArgumentException("Bad family ${this.name}")
}
private val Family.toPodGenTaskName: String private val Family.toPodGenTaskName: String
get() = lowerCamelCaseName( get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_GEN_TASK_NAME, platformLiteral)
KotlinCocoapodsPlugin.POD_GEN_TASK_NAME,
name private val Family.toPodInstallSyntheticTaskName: String
) get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_INSTALL_TASK_NAME, "synthetic", platformLiteral)
private fun String.toSetupBuildTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName( private fun String.toSetupBuildTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName(
KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME, KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME,
@@ -451,23 +460,25 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project: Project, project: Project,
cocoapodsExtension: CocoapodsExtension cocoapodsExtension: CocoapodsExtension
) { ) {
val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) val podspecTaskProvider = project.tasks.named<PodspecTask>(POD_SPEC_TASK_NAME)
project.tasks.register(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) { project.registerTask<PodInstallTask>(POD_INSTALL_TASK_NAME) { task ->
it.group = TASK_GROUP task.group = TASK_GROUP
it.description = "Invokes `pod install` call within Podfile location directory" task.description = "Invokes `pod install` call within Podfile location directory"
it.podfile.set(cocoapodsExtension.podfile) task.podfile.set(project.provider { cocoapodsExtension.podfile })
it.podspec.set(podspecTaskProvider.map { podspecTask -> podspecTask.outputFile }) task.podspec.set(podspecTaskProvider.map { it.outputFile })
it.frameworkName = cocoapodsExtension.podFrameworkName task.frameworkName.set(cocoapodsExtension.podFrameworkName)
it.dependsOn(podspecTaskProvider) task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
task.pods.set(cocoapodsExtension.pods)
task.dependsOn(podspecTaskProvider)
} }
} }
private fun registerPodGenTask( private fun registerSyntheticPodTasks(
project: Project, kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension project: Project, kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension
) { ) {
val families = mutableSetOf<Family>() val families = mutableSetOf<Family>()
val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) val podspecTaskProvider = project.tasks.named<PodspecTask>(POD_SPEC_TASK_NAME)
kotlinExtension.supportedTargets().all { target -> kotlinExtension.supportedTargets().all { target ->
val family = target.konanTarget.family val family = target.konanTarget.family
if (family in families) { if (family in families) {
@@ -483,15 +494,23 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
else -> error("Unknown cocoapods platform: $family") else -> error("Unknown cocoapods platform: $family")
} }
project.tasks.register(family.toPodGenTaskName, PodGenTask::class.java) { val podGenTask = project.registerTask<PodGenTask>(family.toPodGenTaskName) { task ->
it.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
it.podspec = podspecTaskProvider.map { task -> task.outputFile } task.podspec = podspecTaskProvider.map { it.outputFile }
it.podName = project.provider { cocoapodsExtension.name } task.podName = project.provider { cocoapodsExtension.name }
it.useLibraries = project.provider { cocoapodsExtension.useLibraries } task.useLibraries = project.provider { cocoapodsExtension.useLibraries }
it.specRepos = project.provider { cocoapodsExtension.specRepos } task.specRepos = project.provider { cocoapodsExtension.specRepos }
it.family = family task.family = family
it.platformSettings = platformSettings task.platformSettings = platformSettings
it.pods.set(cocoapodsExtension.pods) task.pods.set(cocoapodsExtension.pods)
}
project.registerTask<PodInstallSyntheticTask>(family.toPodInstallSyntheticTaskName) { task ->
task.description = "Invokes `pod install` for synthetic project"
task.podfile.set(podGenTask.map { it.podfile.get() })
task.family.set(family)
task.podName.set(cocoapodsExtension.name)
task.dependsOn(podGenTask)
} }
} }
} }
@@ -520,15 +539,15 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
} }
sdks += sdk sdks += sdk
val podGenTaskProvider = project.tasks.named(target.konanTarget.family.toPodGenTaskName, PodGenTask::class.java) val podInstallTask = project.tasks.named<PodInstallSyntheticTask>(target.konanTarget.family.toPodInstallSyntheticTaskName)
project.tasks.register(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java) { project.registerTask<PodSetupBuildTask>(sdk.toSetupBuildTaskName(pod)) { task ->
it.group = TASK_GROUP task.group = TASK_GROUP
it.description = "Collect environment variables from .xcworkspace file" task.description = "Collect environment variables from .xcworkspace file"
it.pod = project.provider { pod } task.pod = project.provider { pod }
it.sdk = project.provider { sdk } task.sdk = project.provider { sdk }
it.podsXcodeProjDir = podGenTaskProvider.map { podGen -> podGen.podsXcodeProjDir.get() } task.podsXcodeProjDir = podInstallTask.map { it.podsXcodeProjDirProvider.get() }
it.frameworkName = cocoapodsExtension.podFrameworkName task.frameworkName = cocoapodsExtension.podFrameworkName
it.dependsOn(podGenTaskProvider) task.dependsOn(podInstallTask)
} }
} }
} }
@@ -772,7 +791,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
injectPodspecExtensionToArtifacts(project, kotlinArtifactsExtension, cocoapodsExtension) injectPodspecExtensionToArtifacts(project, kotlinArtifactsExtension, cocoapodsExtension)
registerPodspecTask(project, cocoapodsExtension) registerPodspecTask(project, cocoapodsExtension)
registerPodGenTask(project, kotlinExtension, cocoapodsExtension) registerSyntheticPodTasks(project, kotlinExtension, cocoapodsExtension)
registerPodInstallTask(project, cocoapodsExtension) registerPodInstallTask(project, cocoapodsExtension)
registerPodSetupBuildTasks(project, kotlinExtension, cocoapodsExtension) registerPodSetupBuildTasks(project, kotlinExtension, cocoapodsExtension)
registerPodBuildTasks(project, kotlinExtension, cocoapodsExtension) registerPodBuildTasks(project, kotlinExtension, cocoapodsExtension)
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.gradle.targets.native.cocoapods package org.jetbrains.kotlin.gradle.targets.native.cocoapods
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos
@@ -20,8 +19,7 @@ class MissingSpecReposMessage(override val missingInfo: SpecRepos) : MissingInfo
} }
class MissingCocoapodsMessage( class MissingCocoapodsMessage(
override val missingInfo: CocoapodsDependency, override val missingInfo: CocoapodsDependency
private val project: Project
) : MissingInfoMessage<CocoapodsDependency> { ) : MissingInfoMessage<CocoapodsDependency> {
override val missingMessage: String override val missingMessage: String
get() = "pod '${missingInfo.name}'${missingInfo.source?.let { ", ${it.getPodSourcePath()}" }.orEmpty()}" get() = "pod '${missingInfo.name}'${missingInfo.source?.let { ", ${it.getPodSourcePath()}" }.orEmpty()}"
@@ -8,46 +8,30 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree import org.gradle.api.file.FileTree
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
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
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.gradle.plugin.cocoapods.platformLiteral
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingCocoapodsMessage import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingCocoapodsMessage
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingSpecReposMessage import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingSpecReposMessage
import org.jetbrains.kotlin.gradle.tasks.PodspecTask.Companion.retrievePods
import org.jetbrains.kotlin.gradle.tasks.PodspecTask.Companion.retrieveSpecRepos
import org.jetbrains.kotlin.gradle.utils.runCommand import org.jetbrains.kotlin.gradle.utils.runCommand
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.Reader import java.io.Reader
import java.util.* import java.util.*
private val Family.platformLiteral: String
get() = when (this) {
Family.OSX -> "macos"
Family.IOS -> "ios"
Family.TVOS -> "tvos"
Family.WATCHOS -> "watchos"
else -> throw IllegalArgumentException("Bad family ${this.name}")
}
val CocoapodsDependency.schemeName: String val CocoapodsDependency.schemeName: String
get() = name.split("/")[0] get() = name.split("/")[0]
/**
* The task takes the path to the Podfile and calls `pod install`
* to obtain sources or artifacts for the declared dependencies.
* This task is a part of CocoaPods integration infrastructure.
*/
open class CocoapodsTask : DefaultTask() { open class CocoapodsTask : DefaultTask() {
init { init {
@@ -58,61 +42,164 @@ open class CocoapodsTask : DefaultTask() {
} }
open class PodInstallTask : CocoapodsTask() { /**
* The task takes the path to the Podfile and calls `pod install`
* to obtain sources or artifacts for the declared dependencies.
* This task is a part of CocoaPods integration infrastructure.
*/
abstract class AbstractPodInstallTask : CocoapodsTask() {
init { init {
onlyIf { podfile.isPresent } onlyIf { podfile.isPresent }
} }
@get:Internal
internal lateinit var frameworkName: Provider<String>
@get:Optional @get:Optional
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:InputFile @get:InputFile
internal val podfile = project.objects.property(File::class.java) abstract val podfile: Property<File?>
@get:Optional protected val workingDir: Provider<File> = podfile.map { file: File? ->
@get:PathSensitive(PathSensitivity.ABSOLUTE) requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
@get:InputFile }
internal val podspec = project.objects.property(File::class.java)
@get:Optional
@get:OutputDirectory @get:OutputDirectory
internal val podsXcodeProjDirProvider: Provider<File>? internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
get() = podfile.orNull?.let {
project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") } @get:Internal
} internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
@TaskAction @TaskAction
fun doPodInstall() { open fun doPodInstall() {
podfile.orNull?.parentFile?.also { podfileDir -> val podInstallCommand = listOf("pod", "install")
val podInstallCommand = listOf("pod", "install")
runCommand(podInstallCommand, runCommand(podInstallCommand,
logger, logger,
errorHandler = { returnCode, output, _ -> errorHandler = ::handleError,
CocoapodsErrorHandlingUtil.handlePodInstallError( exceptionHandler = { e: IOException ->
podInstallCommand.joinToString(" "), CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
returnCode, },
output, processConfiguration = {
project, directory(workingDir.get())
frameworkName.get() })
)
},
exceptionHandler = { e: IOException ->
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
},
processConfiguration = {
directory(podfileDir)
})
with(podsXcodeProjDirProvider) { with(podsXcodeProjDirProvider.get()) {
check(this != null && get().exists() && get().isDirectory) { check(exists() && 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."
}
} }
} }
} }
abstract fun handleError(retCode: Int, error: String, process: Process): String?
}
abstract class PodInstallTask : AbstractPodInstallTask() {
@get:Optional
@get:InputFile
abstract val podspec: Property<File?>
@get:Input
abstract val frameworkName: Property<String>
@get:Nested
abstract val specRepos: Property<SpecRepos>
@get:Nested
abstract val pods: ListProperty<CocoapodsDependency>
override fun handleError(retCode: Int, error: String, process: Process): String? {
val specReposMessages = MissingSpecReposMessage(specRepos.get()).missingMessage
val cocoapodsMessages = pods.get().map { MissingCocoapodsMessage(it).missingMessage }
return listOfNotNull(
"'pod install' command failed with code $retCode.",
"Error message:",
error.lines().filter { it.isNotBlank() }.joinToString("\n"),
"""
| Please, check that podfile contains following lines in header:
| $specReposMessages
|
""".trimMargin(),
"""
| Please, check that each target depended on ${frameworkName.get()} contains following dependencies:
| ${cocoapodsMessages.joinToString("\n")}
|
""".trimMargin()
).joinToString("\n")
}
}
abstract class PodInstallSyntheticTask : AbstractPodInstallTask() {
@get:Input
abstract val family: Property<Family>
@get:Input
abstract val podName: Property<String>
@get:OutputDirectory
internal val syntheticXcodeProject: Provider<File> = workingDir.map { it.resolve("synthetic.xcodeproj") }
override fun doPodInstall() {
val projResource = "/cocoapods/project.pbxproj"
val projDestination = syntheticXcodeProject.get().resolve("project.pbxproj")
syntheticXcodeProject.get().mkdirs()
projDestination.outputStream().use { file ->
javaClass.getResourceAsStream(projResource)!!.use { resource ->
resource.copyTo(file)
}
}
super.doPodInstall()
}
override fun handleError(retCode: Int, error: String, process: Process): String? {
var message = """
|'pod install' command on the synthetic project failed with return code: $retCode
|
| Error: ${error.lines().filter { it.contains("[!]") }.joinToString("\n")}
|
""".trimMargin()
if (
error.contains("deployment target") ||
error.contains("no platform was specified") ||
error.contains(Regex("The platform of the target .+ is not compatible with `${podName.get()}"))
) {
message += """
|
| Possible reason: ${family.get().platformLiteral} deployment target is not configured
| Configure deployment_target for ALL targets as follows:
| cocoapods {
| ...
| ${family.get().platformLiteral}.deploymentTarget = "..."
| ...
| }
|
""".trimMargin()
return message
} else if (
error.contains("Unable to add a source with url") ||
error.contains("Couldn't determine repo name for URL") ||
error.contains("Unable to find a specification")
) {
message += """
|
| Possible reason: spec repos are not configured correctly.
| Ensure that spec repos are correctly configured for all private pod dependencies:
| cocoapods {
| specRepos {
| url("<private spec repo url>")
| }
| }
|
""".trimMargin()
return message
} else {
return null
}
}
} }
/** /**
@@ -126,82 +213,39 @@ abstract class PodGenTask : CocoapodsTask() {
} }
} }
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:InputFile @get:InputFile
internal lateinit var podspec: Provider<File> internal abstract val podspec: Property<File>
@get:Input @get:Input
internal lateinit var podName: Provider<String> internal abstract val podName: Property<String>
@get:Input @get:Input
internal lateinit var useLibraries: Provider<Boolean> internal abstract val useLibraries: Property<Boolean>
@get:Internal @get:Input
lateinit var family: Family internal abstract val family: Property<Family>
@get:Nested @get:Nested
internal lateinit var platformSettings: PodspecPlatformSettings internal abstract val platformSettings: Property<PodspecPlatformSettings>
@get:Nested @get:Nested
internal lateinit var specRepos: Provider<SpecRepos> internal abstract val specRepos: Property<SpecRepos>
@get:Nested @get:Nested
abstract val pods: ListProperty<CocoapodsDependency> internal abstract val pods: ListProperty<CocoapodsDependency>
@get:OutputDirectory @get:OutputFile
internal val outputDir: Provider<File> = project.provider { project.cocoapodsBuildDirs.synthetic(family) } val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
@get:Internal
internal val podsXcodeProjDir: Provider<File> = outputDir.map { it.resolve("Pods").resolve("Pods.xcodeproj") }
@get:Internal
val podfile: Provider<File> = outputDir.map { it.resolve("Podfile") }
@TaskAction @TaskAction
fun generate() { fun generate() {
val syntheticDir = outputDir.get().apply { mkdirs() }
val specRepos = specRepos.get().getAll() val specRepos = specRepos.get().getAll()
val projResource = "/cocoapods/project.pbxproj"
val projDestination = syntheticDir.resolve("synthetic.xcodeproj").resolve("project.pbxproj")
projDestination.parentFile.mkdirs()
projDestination.outputStream().use { file ->
javaClass.getResourceAsStream(projResource)!!.use { resource ->
resource.copyTo(file)
}
}
val podfile = this.podfile.get() val podfile = this.podfile.get()
podfile.createNewFile() podfile.createNewFile()
val podfileContent = getPodfileContent(specRepos, family.platformLiteral) val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
podfile.writeText(podfileContent) podfile.writeText(podfileContent)
val podInstallCommand = listOf("pod", "install")
runCommand(
podInstallCommand,
project.logger,
exceptionHandler = { e: IOException ->
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
},
errorHandler = { retCode, output, _ ->
CocoapodsErrorHandlingUtil.handlePodInstallSyntheticError(
podInstallCommand.joinToString(" "),
retCode,
output,
family,
podName.get()
)
},
processConfiguration = {
directory(syntheticDir)
})
val podsXcprojFile = podsXcodeProjDir.get()
check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) {
"Synthetic project '${podsXcprojFile.path}' was not created."
}
} }
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) = private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
@@ -215,11 +259,12 @@ abstract class PodGenTask : CocoapodsTask() {
if (useLibraries.get().not()) { if (useLibraries.get().not()) {
appendLine("\tuse_frameworks!") appendLine("\tuse_frameworks!")
} }
val deploymentTarget = platformSettings.deploymentTarget val settings = platformSettings.get()
val deploymentTarget = settings.deploymentTarget
if (deploymentTarget != null) { if (deploymentTarget != null) {
appendLine("\tplatform :${platformSettings.name}, '$deploymentTarget'") appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
} else { } else {
appendLine("\tplatform :${platformSettings.name}") appendLine("\tplatform :${settings.name}")
} }
pods.get().mapNotNull { pods.get().mapNotNull {
buildString { buildString {
@@ -251,6 +296,7 @@ abstract class PodGenTask : CocoapodsTask() {
end end
""".trimIndent() """.trimIndent()
) )
appendLine()
} }
} }
@@ -403,6 +449,7 @@ data class PodBuildSettingsProperties(
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
with(Properties()) { with(Properties()) {
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
load(reader) load(reader)
return PodBuildSettingsProperties( return PodBuildSettingsProperties(
readProperty(BUILD_DIR), readProperty(BUILD_DIR),
@@ -448,79 +495,4 @@ private object CocoapodsErrorHandlingUtil {
} }
} }
fun handlePodInstallError(command: String, 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.",
"Full command: $command",
"Error message:",
error.lines().filter { it.isNotBlank() }.joinToString("\n"),
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 handlePodInstallSyntheticError(command: String, retCode: Int, error: String, family: Family, podName: String): String? {
var message = """
|'pod install' command on the synthetic project failed with return code: $retCode
|
| Full command: $command
|
| Error: ${error.lines().filter { it.contains("[!]") }.joinToString("\n")}
|
""".trimMargin()
if (
error.contains("deployment target") ||
error.contains("no platform was specified") ||
error.contains(Regex("The platform of the target .+ is not compatible with `$podName"))
) {
message += """
|
| Possible reason: ${family.name.toLowerCaseAsciiOnly()} deployment target is not configured
| Configure deployment_target for ALL targets as follows:
| cocoapods {
| ...
| ${family.name.toLowerCaseAsciiOnly()}.deploymentTarget = "..."
| ...
| }
|
""".trimMargin()
return message
} else if (
error.contains("Unable to add a source with url") ||
error.contains("Couldn't determine repo name for URL") ||
error.contains("Unable to find a specification")
) {
message += """
|
| Possible reason: spec repos are not configured correctly.
| Ensure that spec repos are correctly configured for all private pod dependencies:
| cocoapods {
| specRepos {
| url("<private spec repo url>")
| }
| }
|
""".trimMargin()
return message
}
return null
}
} }
@@ -221,8 +221,6 @@ open class PodspecTask : DefaultTask() {
else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|| (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false) || (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false)
internal fun retrieveSpecRepos(project: Project): SpecRepos? = project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.specRepos
internal fun retrievePods(project: Project): List<CocoapodsDependency>? = project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podsAsTaskInput
} }
} }