[Gradle] Split CocoapodsTasks and AdvancedCocoapodsTasks into separate files (2/2)
This commit is contained in:
committed by
Space Team
parent
fc68873df7
commit
7aeca2fda0
+1
-447
@@ -3,434 +3,14 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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.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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
@@ -475,7 +55,6 @@ data class PodBuildSettingsProperties(
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
@@ -497,28 +76,3 @@ 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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-436
@@ -3,44 +3,16 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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.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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The task takes the path to the Podfile and calls `pod install`
|
||||
@@ -91,413 +63,6 @@ abstract class AbstractPodInstallTask : CocoapodsTask() {
|
||||
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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
getProperty(propertyName)
|
||||
}
|
||||
}
|
||||
|
||||
private object CocoapodsErrorHandlingUtil {
|
||||
fun handle(e: IOException, command: List<String>) {
|
||||
if (e.message?.contains("No such file or directory") == true) {
|
||||
|
||||
+2
-503
@@ -3,36 +3,17 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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.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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
@@ -40,485 +21,3 @@ open class CocoapodsTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-299
@@ -7,310 +7,16 @@
|
||||
package org.jetbrains.kotlin.gradle.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.wrapper.Wrapper
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Nested
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
|
||||
import org.jetbrains.kotlin.gradle.utils.appendLine
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The task generates a podspec file which allows a user to
|
||||
* integrate a Kotlin/Native framework into a CocoaPods project.
|
||||
*/
|
||||
open class PodspecTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
internal val specName = project.objects.property(String::class.java)
|
||||
|
||||
@get:Internal
|
||||
internal val outputDir = project.objects.property(File::class.java)
|
||||
|
||||
@get:OutputFile
|
||||
val outputFile: File
|
||||
get() = outputDir.get().resolve("${specName.get()}.podspec")
|
||||
|
||||
@get:Input
|
||||
internal lateinit var needPodspec: Provider<Boolean>
|
||||
|
||||
@get:Nested
|
||||
val pods = project.objects.listProperty(CocoapodsDependency::class.java)
|
||||
|
||||
@get:Input
|
||||
internal val version = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
internal val publishing = project.objects.property(Boolean::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val source = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val homepage = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val license = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val authors = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val summary = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val extraSpecAttributes = project.objects.mapProperty(String::class.java, String::class.java)
|
||||
|
||||
@get:Input
|
||||
internal lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var ios: Provider<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var osx: Provider<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var tvos: Provider<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var watchos: Provider<PodspecPlatformSettings>
|
||||
|
||||
init {
|
||||
onlyIf { needPodspec.get() }
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
|
||||
check(version.get() != Project.DEFAULT_VERSION) {
|
||||
"""
|
||||
Cocoapods Integration requires pod version to be specified.
|
||||
Please specify pod version by adding 'version = "<version>"' to the cocoapods block.
|
||||
Alternatively, specify the version for the entire project explicitly.
|
||||
Pod version format has to conform podspec syntax requirements: https://guides.cocoapods.org/syntax/podspec.html#version
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val gradleWrapper = (project.rootProject.tasks.getByName("wrapper") as? Wrapper)?.scriptFile
|
||||
require(gradleWrapper != null && gradleWrapper.exists()) {
|
||||
"""
|
||||
The Gradle wrapper is required to run the build from Xcode.
|
||||
|
||||
Please run the same command with `-P$GENERATE_WRAPPER_PROPERTY=true` or run the `:wrapper` task to generate the wrapper manually.
|
||||
|
||||
See details about the wrapper at https://docs.gradle.org/current/userguide/gradle_wrapper.html
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val deploymentTargets = run {
|
||||
listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") {
|
||||
if (extraSpecAttributes.get().containsKey("${it.name}.deployment_target")) "" else "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'"
|
||||
}
|
||||
}
|
||||
|
||||
val dependencies = pods.get().map { pod ->
|
||||
val versionSuffix = if (pod.version != null) ", '${pod.version}'" else ""
|
||||
"| spec.dependency '${pod.name}'$versionSuffix"
|
||||
}.joinToString(separator = "\n")
|
||||
|
||||
val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile)
|
||||
val vendoredFramework = if (publishing.get()) "${frameworkName.get()}.xcframework" else frameworkDir.resolve("${frameworkName.get()}.framework").invariantSeparatorsPath
|
||||
val vendoredFrameworks = if (extraSpecAttributes.get().containsKey("vendored_frameworks")) "" else "| spec.vendored_frameworks = '$vendoredFramework'"
|
||||
|
||||
val libraries = if (extraSpecAttributes.get().containsKey("libraries")) "" else "| spec.libraries = 'c++'"
|
||||
|
||||
val xcConfig = if (publishing.get() || extraSpecAttributes.get().containsKey("pod_target_xcconfig")) "" else
|
||||
""" |
|
||||
| spec.pod_target_xcconfig = {
|
||||
| 'KOTLIN_PROJECT_PATH' => '${if (project.depth != 0) project.path else ""}',
|
||||
| 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}',
|
||||
| }
|
||||
""".trimMargin()
|
||||
|
||||
val gradleCommand = "\$REPO_ROOT/${gradleWrapper.relativeTo(project.projectDir).invariantSeparatorsPath}"
|
||||
val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else
|
||||
""" |
|
||||
| spec.script_phases = [
|
||||
| {
|
||||
| :name => 'Build ${specName.get()}',
|
||||
| :execution_position => :before_compile,
|
||||
| :shell_path => '/bin/sh',
|
||||
| :script => <<-SCRIPT
|
||||
| if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
| echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
| exit 0
|
||||
| fi
|
||||
| set -ev
|
||||
| REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
|
||||
| "$gradleCommand" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:$SYNC_TASK_NAME \
|
||||
| -P${KotlinCocoapodsPlugin.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \
|
||||
| -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \
|
||||
| -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION"
|
||||
| SCRIPT
|
||||
| }
|
||||
| ]
|
||||
""".trimMargin()
|
||||
|
||||
val customSpec = extraSpecAttributes.get().map { "| spec.${it.key} = ${it.value}" }.joinToString("\n")
|
||||
|
||||
with(outputFile) {
|
||||
writeText(
|
||||
"""
|
||||
|Pod::Spec.new do |spec|
|
||||
| spec.name = '${specName.get()}'
|
||||
| spec.version = '${version.get()}'
|
||||
| spec.homepage = ${homepage.getOrEmpty().surroundWithSingleQuotesIfNeeded()}
|
||||
| spec.source = ${source.getOrElse("{ :http=> ''}")}
|
||||
| spec.authors = ${authors.getOrEmpty().surroundWithSingleQuotesIfNeeded()}
|
||||
| spec.license = ${license.getOrEmpty().surroundWithSingleQuotesIfNeeded()}
|
||||
| spec.summary = '${summary.getOrEmpty()}'
|
||||
$vendoredFrameworks
|
||||
$libraries
|
||||
$deploymentTargets
|
||||
$dependencies
|
||||
$xcConfig
|
||||
$scriptPhase
|
||||
$customSpec
|
||||
|end
|
||||
""".trimMargin()
|
||||
)
|
||||
|
||||
if (hasPodfileOwnOrParent(project) && publishing.get().not()) {
|
||||
logger.quiet(
|
||||
"""
|
||||
Generated a podspec file at: ${absolutePath}.
|
||||
To include it in your Xcode project, check that the following dependency snippet exists in your Podfile:
|
||||
|
||||
pod '${specName.get()}', :path => '${parentFile.absolutePath}'
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun Provider<String>.getOrEmpty(): String = getOrElse("")
|
||||
|
||||
private fun String.surroundWithSingleQuotesIfNeeded(): String =
|
||||
if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'"
|
||||
|
||||
companion object {
|
||||
private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension?
|
||||
get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension
|
||||
|
||||
private fun hasPodfileOwnOrParent(project: Project): Boolean =
|
||||
if (project.rootProject == project) project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|
||||
else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|
||||
|| (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a dummy framework in the target directory.
|
||||
*
|
||||
* We represent a Kotlin/Native module to CocoaPods as a vendored framework.
|
||||
* CocoaPods needs access to such frameworks during installation process to obtain
|
||||
* their type (static or dynamic) and configure the Xcode project accordingly.
|
||||
* But we cannot build the real framework before installation because it may
|
||||
* depend on CocoaPods libraries which are not downloaded and built at this stage.
|
||||
* So we create a dummy static framework to allow CocoaPods install our pod correctly
|
||||
* and then replace it with the real one during a real build process.
|
||||
*/
|
||||
abstract class DummyFrameworkTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
abstract val frameworkName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val useStaticFramework: Property<Boolean>
|
||||
|
||||
@get:OutputDirectory
|
||||
val outputFramework: Provider<File> = project.provider { project.cocoapodsBuildDirs.dummyFramework }
|
||||
|
||||
private val dummyFrameworkResource: String
|
||||
get() {
|
||||
val staticOrDynamic = if (!useStaticFramework.get()) "dynamic" else "static"
|
||||
return "/cocoapods/$staticOrDynamic/dummy.framework/"
|
||||
}
|
||||
|
||||
private fun copyResource(from: String, to: File) {
|
||||
to.parentFile.mkdirs()
|
||||
to.outputStream().use { file ->
|
||||
javaClass.getResourceAsStream(from)!!.use { resource ->
|
||||
resource.copyTo(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyTextResource(from: String, to: File, transform: (String) -> String = { it }) {
|
||||
to.parentFile.mkdirs()
|
||||
to.printWriter().use { file ->
|
||||
javaClass.getResourceAsStream(from)!!.use {
|
||||
it.reader().forEachLine { str ->
|
||||
file.println(transform(str))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) =
|
||||
copyResource(
|
||||
"$dummyFrameworkResource$relativeFrom",
|
||||
outputFramework.get().resolve(relativeTo)
|
||||
)
|
||||
|
||||
private fun copyFrameworkTextFile(
|
||||
relativeFrom: String,
|
||||
relativeTo: String = relativeFrom,
|
||||
transform: (String) -> String = { it }
|
||||
) = copyTextResource(
|
||||
"$dummyFrameworkResource$relativeFrom",
|
||||
outputFramework.get().resolve(relativeTo),
|
||||
transform
|
||||
)
|
||||
|
||||
@TaskAction
|
||||
fun create() {
|
||||
// Reset the destination directory
|
||||
with(outputFramework.get()) {
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
// Copy files for the dummy framework.
|
||||
copyFrameworkFile("Info.plist")
|
||||
copyFrameworkFile("dummy", frameworkName.get())
|
||||
copyFrameworkFile("Headers/placeholder.h")
|
||||
copyFrameworkTextFile("Modules/module.modulemap") {
|
||||
if (it == "framework module dummy {") {
|
||||
it.replace("dummy", frameworkName.get())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a def-file for the given CocoaPods dependency.
|
||||
*/
|
||||
|
||||
+3
-255
@@ -7,224 +7,14 @@
|
||||
package org.jetbrains.kotlin.gradle.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.wrapper.Wrapper
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
|
||||
import org.jetbrains.kotlin.gradle.utils.appendLine
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The task generates a podspec file which allows a user to
|
||||
* integrate a Kotlin/Native framework into a CocoaPods project.
|
||||
*/
|
||||
open class PodspecTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
internal val specName = project.objects.property(String::class.java)
|
||||
|
||||
@get:Internal
|
||||
internal val outputDir = project.objects.property(File::class.java)
|
||||
|
||||
@get:OutputFile
|
||||
val outputFile: File
|
||||
get() = outputDir.get().resolve("${specName.get()}.podspec")
|
||||
|
||||
@get:Input
|
||||
internal lateinit var needPodspec: Provider<Boolean>
|
||||
|
||||
@get:Nested
|
||||
val pods = project.objects.listProperty(CocoapodsDependency::class.java)
|
||||
|
||||
@get:Input
|
||||
internal val version = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
internal val publishing = project.objects.property(Boolean::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val source = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val homepage = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val license = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val authors = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val summary = project.objects.property(String::class.java)
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val extraSpecAttributes = project.objects.mapProperty(String::class.java, String::class.java)
|
||||
|
||||
@get:Input
|
||||
internal lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var ios: Provider<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var osx: Provider<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var tvos: Provider<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var watchos: Provider<PodspecPlatformSettings>
|
||||
|
||||
init {
|
||||
onlyIf { needPodspec.get() }
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
|
||||
check(version.get() != Project.DEFAULT_VERSION) {
|
||||
"""
|
||||
Cocoapods Integration requires pod version to be specified.
|
||||
Please specify pod version by adding 'version = "<version>"' to the cocoapods block.
|
||||
Alternatively, specify the version for the entire project explicitly.
|
||||
Pod version format has to conform podspec syntax requirements: https://guides.cocoapods.org/syntax/podspec.html#version
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val gradleWrapper = (project.rootProject.tasks.getByName("wrapper") as? Wrapper)?.scriptFile
|
||||
require(gradleWrapper != null && gradleWrapper.exists()) {
|
||||
"""
|
||||
The Gradle wrapper is required to run the build from Xcode.
|
||||
|
||||
Please run the same command with `-P$GENERATE_WRAPPER_PROPERTY=true` or run the `:wrapper` task to generate the wrapper manually.
|
||||
|
||||
See details about the wrapper at https://docs.gradle.org/current/userguide/gradle_wrapper.html
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val deploymentTargets = run {
|
||||
listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") {
|
||||
if (extraSpecAttributes.get().containsKey("${it.name}.deployment_target")) "" else "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'"
|
||||
}
|
||||
}
|
||||
|
||||
val dependencies = pods.get().map { pod ->
|
||||
val versionSuffix = if (pod.version != null) ", '${pod.version}'" else ""
|
||||
"| spec.dependency '${pod.name}'$versionSuffix"
|
||||
}.joinToString(separator = "\n")
|
||||
|
||||
val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile)
|
||||
val vendoredFramework = if (publishing.get()) "${frameworkName.get()}.xcframework" else frameworkDir.resolve("${frameworkName.get()}.framework").invariantSeparatorsPath
|
||||
val vendoredFrameworks = if (extraSpecAttributes.get().containsKey("vendored_frameworks")) "" else "| spec.vendored_frameworks = '$vendoredFramework'"
|
||||
|
||||
val libraries = if (extraSpecAttributes.get().containsKey("libraries")) "" else "| spec.libraries = 'c++'"
|
||||
|
||||
val xcConfig = if (publishing.get() || extraSpecAttributes.get().containsKey("pod_target_xcconfig")) "" else
|
||||
""" |
|
||||
| spec.pod_target_xcconfig = {
|
||||
| 'KOTLIN_PROJECT_PATH' => '${if (project.depth != 0) project.path else ""}',
|
||||
| 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}',
|
||||
| }
|
||||
""".trimMargin()
|
||||
|
||||
val gradleCommand = "\$REPO_ROOT/${gradleWrapper.relativeTo(project.projectDir).invariantSeparatorsPath}"
|
||||
val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else
|
||||
""" |
|
||||
| spec.script_phases = [
|
||||
| {
|
||||
| :name => 'Build ${specName.get()}',
|
||||
| :execution_position => :before_compile,
|
||||
| :shell_path => '/bin/sh',
|
||||
| :script => <<-SCRIPT
|
||||
| if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
| echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
| exit 0
|
||||
| fi
|
||||
| set -ev
|
||||
| REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
|
||||
| "$gradleCommand" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:$SYNC_TASK_NAME \
|
||||
| -P${KotlinCocoapodsPlugin.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \
|
||||
| -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \
|
||||
| -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION"
|
||||
| SCRIPT
|
||||
| }
|
||||
| ]
|
||||
""".trimMargin()
|
||||
|
||||
val customSpec = extraSpecAttributes.get().map { "| spec.${it.key} = ${it.value}" }.joinToString("\n")
|
||||
|
||||
with(outputFile) {
|
||||
writeText(
|
||||
"""
|
||||
|Pod::Spec.new do |spec|
|
||||
| spec.name = '${specName.get()}'
|
||||
| spec.version = '${version.get()}'
|
||||
| spec.homepage = ${homepage.getOrEmpty().surroundWithSingleQuotesIfNeeded()}
|
||||
| spec.source = ${source.getOrElse("{ :http=> ''}")}
|
||||
| spec.authors = ${authors.getOrEmpty().surroundWithSingleQuotesIfNeeded()}
|
||||
| spec.license = ${license.getOrEmpty().surroundWithSingleQuotesIfNeeded()}
|
||||
| spec.summary = '${summary.getOrEmpty()}'
|
||||
$vendoredFrameworks
|
||||
$libraries
|
||||
$deploymentTargets
|
||||
$dependencies
|
||||
$xcConfig
|
||||
$scriptPhase
|
||||
$customSpec
|
||||
|end
|
||||
""".trimMargin()
|
||||
)
|
||||
|
||||
if (hasPodfileOwnOrParent(project) && publishing.get().not()) {
|
||||
logger.quiet(
|
||||
"""
|
||||
Generated a podspec file at: ${absolutePath}.
|
||||
To include it in your Xcode project, check that the following dependency snippet exists in your Podfile:
|
||||
|
||||
pod '${specName.get()}', :path => '${parentFile.absolutePath}'
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun Provider<String>.getOrEmpty(): String = getOrElse("")
|
||||
|
||||
private fun String.surroundWithSingleQuotesIfNeeded(): String =
|
||||
if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'"
|
||||
|
||||
companion object {
|
||||
private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension?
|
||||
get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension
|
||||
|
||||
private fun hasPodfileOwnOrParent(project: Project): Boolean =
|
||||
if (project.rootProject == project) project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|
||||
else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|
||||
|| (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a dummy framework in the target directory.
|
||||
*
|
||||
@@ -310,45 +100,3 @@ abstract class DummyFrameworkTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a def-file for the given CocoaPods dependency.
|
||||
*/
|
||||
abstract class DefFileTask : DefaultTask() {
|
||||
|
||||
@get:Nested
|
||||
abstract val pod: Property<CocoapodsDependency>
|
||||
|
||||
@get:Input
|
||||
abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:OutputFile
|
||||
val outputFile: File
|
||||
get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def")
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
outputFile.parentFile.mkdirs()
|
||||
outputFile.writeText(buildString {
|
||||
appendLine("language = Objective-C")
|
||||
with(pod.get()) {
|
||||
when {
|
||||
headers != null -> appendLine("headers = $headers")
|
||||
useLibraries.get() -> logger.warn(
|
||||
"""
|
||||
w: Pod '$moduleName' should have 'headers' property specified when using 'useLibraries()'.
|
||||
Otherwise code from this pod won't be accessible from Kotlin.
|
||||
""".trimIndent()
|
||||
)
|
||||
else -> {
|
||||
appendLine("modules = $moduleName")
|
||||
|
||||
// Linker opt with framework name is added so produced cinterop klib would have this flag inside its manifest
|
||||
// This way error will be more obvious when someone will try to depend on a library with this cinterop
|
||||
appendLine("linkerOpts = -framework $moduleName")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-448
@@ -3,374 +3,20 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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.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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
@@ -429,96 +75,3 @@ open class PodBuildTask : CocoapodsTask() {
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-409
@@ -3,230 +3,19 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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.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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
@@ -325,200 +114,3 @@ abstract class PodGenTask : CocoapodsTask() {
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-438
@@ -3,157 +3,17 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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.cocoapodsBuildDirs
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -227,298 +87,3 @@ abstract class PodInstallSyntheticTask : AbstractPodInstallTask() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-444
@@ -3,93 +3,20 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos
|
||||
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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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() {
|
||||
|
||||
@@ -154,371 +81,3 @@ abstract class PodInstallTask : AbstractPodInstallTask() {
|
||||
).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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:Input
|
||||
lateinit var frameworkName: Provider<String>
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@get:Nested
|
||||
lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val buildSettingsFile: Provider<File> = project.provider {
|
||||
project.cocoapodsBuildDirs
|
||||
.buildSettings
|
||||
.resolve(getBuildSettingFileName(pod.get(), sdk.get()))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun setupBuild() {
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val buildSettingsReceivingCommand = listOf(
|
||||
"xcodebuild", "-showBuildSettings",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get()
|
||||
)
|
||||
|
||||
val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
|
||||
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader())
|
||||
buildSettingsFile.get().let { bsf ->
|
||||
buildSettingsProperties.writeSettings(bsf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-466
@@ -3,329 +3,16 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle
|
||||
@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
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
|
||||
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.MissingSpecReposMessage
|
||||
import org.jetbrains.kotlin.gradle.utils.runCommand
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.util.*
|
||||
|
||||
val CocoapodsDependency.schemeName: String
|
||||
get() = name.split("/")[0]
|
||||
|
||||
|
||||
open class CocoapodsTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
HostManager.hostIsMac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
onlyIf { podfile.isPresent }
|
||||
}
|
||||
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val podfile: Property<File?>
|
||||
|
||||
@get:Internal
|
||||
protected val workingDir: Provider<File> = podfile.map { file: File? ->
|
||||
requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile
|
||||
}
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podsDir: Provider<File> = workingDir.map { it.resolve("Pods") }
|
||||
|
||||
@get:Internal
|
||||
internal val podsXcodeProjDirProvider: Provider<File> = podsDir.map { it.resolve("Pods.xcodeproj") }
|
||||
|
||||
@TaskAction
|
||||
open fun doPodInstall() {
|
||||
val podInstallCommand = listOf("pod", "install")
|
||||
|
||||
runCommand(podInstallCommand,
|
||||
logger,
|
||||
errorHandler = ::handleError,
|
||||
exceptionHandler = { e: IOException ->
|
||||
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
|
||||
},
|
||||
processConfiguration = {
|
||||
directory(workingDir.get())
|
||||
})
|
||||
|
||||
with(podsXcodeProjDirProvider.get()) {
|
||||
check(exists() && isDirectory) {
|
||||
"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>
|
||||
|
||||
@get:InputDirectory
|
||||
abstract val dummyFramework: Property<File>
|
||||
|
||||
private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") }
|
||||
private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } }
|
||||
|
||||
override fun doPodInstall() {
|
||||
// We always need to execute 'pod install' with the dummy framework because the one left from a previous build
|
||||
// may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back
|
||||
framework.rename(tmpFramework)
|
||||
dummyFramework.rename(framework)
|
||||
super.doPodInstall()
|
||||
framework.rename(dummyFramework)
|
||||
tmpFramework.rename(framework)
|
||||
}
|
||||
|
||||
private fun Provider<File>.rename(dest: Provider<File>) = get().rename(dest.get())
|
||||
|
||||
private fun File.rename(dest: File) {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The task generates a synthetic project with all cocoapods dependencies
|
||||
*/
|
||||
abstract class PodGenTask : CocoapodsTask() {
|
||||
|
||||
init {
|
||||
onlyIf {
|
||||
pods.get().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@get:InputFile
|
||||
internal abstract val podspec: Property<File>
|
||||
|
||||
@get:Input
|
||||
internal abstract val podName: Property<String>
|
||||
|
||||
@get:Input
|
||||
internal abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:Input
|
||||
internal abstract val family: Property<Family>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val platformSettings: Property<PodspecPlatformSettings>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val specRepos: Property<SpecRepos>
|
||||
|
||||
@get:Nested
|
||||
internal abstract val pods: ListProperty<CocoapodsDependency>
|
||||
|
||||
@get:OutputFile
|
||||
val podfile: Provider<File> = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") }
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val specRepos = specRepos.get().getAll()
|
||||
|
||||
val podfile = this.podfile.get()
|
||||
podfile.createNewFile()
|
||||
|
||||
val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral)
|
||||
podfile.writeText(podfileContent)
|
||||
}
|
||||
|
||||
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
|
||||
buildString {
|
||||
|
||||
specRepos.forEach {
|
||||
appendLine("source '$it'")
|
||||
}
|
||||
|
||||
appendLine("target '$xcodeTarget' do")
|
||||
if (useLibraries.get().not()) {
|
||||
appendLine("\tuse_frameworks!")
|
||||
}
|
||||
val settings = platformSettings.get()
|
||||
val deploymentTarget = settings.deploymentTarget
|
||||
if (deploymentTarget != null) {
|
||||
appendLine("\tplatform :${settings.name}, '$deploymentTarget'")
|
||||
} else {
|
||||
appendLine("\tplatform :${settings.name}")
|
||||
}
|
||||
pods.get().mapNotNull {
|
||||
buildString {
|
||||
append("pod '${it.name}'")
|
||||
|
||||
val version = it.version
|
||||
val source = it.source
|
||||
|
||||
if (source != null) {
|
||||
append(", ${source.getPodSourcePath()}")
|
||||
} else if (version != null) {
|
||||
append(", '$version'")
|
||||
}
|
||||
|
||||
}
|
||||
}.forEach { appendLine("\t$it") }
|
||||
appendLine("end\n")
|
||||
//disable signing for all synthetic pods KT-54314
|
||||
append(
|
||||
"""
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
|
||||
end
|
||||
end
|
||||
end
|
||||
""".trimIndent()
|
||||
)
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodSetupBuildTask : CocoapodsTask() {
|
||||
|
||||
@@ -371,154 +58,3 @@ open class PodSetupBuildTask : CocoapodsTask() {
|
||||
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
|
||||
"build-settings-$sdk-${pod.schemeName}.properties"
|
||||
|
||||
/**
|
||||
* The task compiles external cocoa pods sources.
|
||||
*/
|
||||
open class PodBuildTask : CocoapodsTask() {
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:InputFile
|
||||
lateinit var buildSettingsFile: Provider<File>
|
||||
internal set
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var pod: Provider<CocoapodsDependency>
|
||||
|
||||
@get:PathSensitive(PathSensitivity.ABSOLUTE)
|
||||
@get:IgnoreEmptyDirectories
|
||||
@get:InputFiles
|
||||
internal val srcDir: FileTree
|
||||
get() = project.fileTree(
|
||||
buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot }
|
||||
)
|
||||
|
||||
@get:Internal
|
||||
internal var buildDir: Provider<File> = project.provider {
|
||||
project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
internal lateinit var sdk: Provider<String>
|
||||
|
||||
@Suppress("unused") // declares an ouptut
|
||||
@get:OutputFiles
|
||||
internal val buildResult: Provider<FileCollection> = project.provider {
|
||||
project.fileTree(buildDir.get()) {
|
||||
it.include("**/${pod.get().schemeName}.*/")
|
||||
it.include("**/${pod.get().schemeName}/")
|
||||
}
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var podsXcodeProjDir: Provider<File>
|
||||
|
||||
@TaskAction
|
||||
fun buildDependencies() {
|
||||
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader())
|
||||
|
||||
val podsXcodeProjDir = podsXcodeProjDir.get()
|
||||
|
||||
val podXcodeBuildCommand = listOf(
|
||||
"xcodebuild",
|
||||
"-project", podsXcodeProjDir.name,
|
||||
"-scheme", pod.get().schemeName,
|
||||
"-sdk", sdk.get(),
|
||||
"-configuration", podBuildSettings.configuration
|
||||
)
|
||||
|
||||
runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class PodBuildSettingsProperties(
|
||||
internal val buildDir: String,
|
||||
internal val configuration: String,
|
||||
val configurationBuildDir: String,
|
||||
internal val podsTargetSrcRoot: String,
|
||||
internal val cflags: String? = null,
|
||||
internal val headerPaths: String? = null,
|
||||
internal val publicHeadersFolderPath: String? = null,
|
||||
internal val frameworkPaths: String? = null
|
||||
) {
|
||||
|
||||
fun writeSettings(
|
||||
buildSettingsFile: File
|
||||
) {
|
||||
buildSettingsFile.parentFile.mkdirs()
|
||||
buildSettingsFile.delete()
|
||||
buildSettingsFile.createNewFile()
|
||||
|
||||
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
|
||||
|
||||
with(buildSettingsFile) {
|
||||
appendText("$BUILD_DIR=$buildDir\n")
|
||||
appendText("$CONFIGURATION=$configuration\n")
|
||||
appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
|
||||
appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
|
||||
cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
|
||||
headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
|
||||
publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") }
|
||||
frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BUILD_DIR = "BUILD_DIR"
|
||||
const val CONFIGURATION = "CONFIGURATION"
|
||||
const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
|
||||
const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
|
||||
const val OTHER_CFLAGS = "OTHER_CFLAGS"
|
||||
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
|
||||
const val PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH"
|
||||
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
|
||||
|
||||
fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties {
|
||||
with(Properties()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here
|
||||
load(reader)
|
||||
return PodBuildSettingsProperties(
|
||||
readProperty(BUILD_DIR),
|
||||
readProperty(CONFIGURATION),
|
||||
readProperty(CONFIGURATION_BUILD_DIR),
|
||||
readProperty(PODS_TARGET_SRCROOT),
|
||||
readNullableProperty(OTHER_CFLAGS),
|
||||
readNullableProperty(HEADER_SEARCH_PATHS),
|
||||
readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH),
|
||||
readNullableProperty(FRAMEWORK_SEARCH_PATHS)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.readProperty(propertyName: String) =
|
||||
readNullableProperty(propertyName) ?: error("$propertyName property is absent")
|
||||
|
||||
private fun Properties.readNullableProperty(propertyName: String) =
|
||||
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}
|
||||
|
|
||||
| Full command: ${command.joinToString(" ")}
|
||||
|
|
||||
| Possible reason: CocoaPods is not installed
|
||||
| Please check that CocoaPods v1.10 or above is installed.
|
||||
|
|
||||
| To check CocoaPods version type 'pod --version' in the terminal
|
||||
|
|
||||
| To install CocoaPods execute 'sudo gem install cocoapods'
|
||||
|
|
||||
""".trimMargin()
|
||||
throw IllegalStateException(message)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-132
@@ -9,20 +9,19 @@ package org.jetbrains.kotlin.gradle.tasks
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.wrapper.Wrapper
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
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
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.PodspecPlatformSettings
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
|
||||
import org.jetbrains.kotlin.gradle.utils.appendLine
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -223,132 +222,3 @@ open class PodspecTask : DefaultTask() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a dummy framework in the target directory.
|
||||
*
|
||||
* We represent a Kotlin/Native module to CocoaPods as a vendored framework.
|
||||
* CocoaPods needs access to such frameworks during installation process to obtain
|
||||
* their type (static or dynamic) and configure the Xcode project accordingly.
|
||||
* But we cannot build the real framework before installation because it may
|
||||
* depend on CocoaPods libraries which are not downloaded and built at this stage.
|
||||
* So we create a dummy static framework to allow CocoaPods install our pod correctly
|
||||
* and then replace it with the real one during a real build process.
|
||||
*/
|
||||
abstract class DummyFrameworkTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
abstract val frameworkName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val useStaticFramework: Property<Boolean>
|
||||
|
||||
@get:OutputDirectory
|
||||
val outputFramework: Provider<File> = project.provider { project.cocoapodsBuildDirs.dummyFramework }
|
||||
|
||||
private val dummyFrameworkResource: String
|
||||
get() {
|
||||
val staticOrDynamic = if (!useStaticFramework.get()) "dynamic" else "static"
|
||||
return "/cocoapods/$staticOrDynamic/dummy.framework/"
|
||||
}
|
||||
|
||||
private fun copyResource(from: String, to: File) {
|
||||
to.parentFile.mkdirs()
|
||||
to.outputStream().use { file ->
|
||||
javaClass.getResourceAsStream(from)!!.use { resource ->
|
||||
resource.copyTo(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyTextResource(from: String, to: File, transform: (String) -> String = { it }) {
|
||||
to.parentFile.mkdirs()
|
||||
to.printWriter().use { file ->
|
||||
javaClass.getResourceAsStream(from)!!.use {
|
||||
it.reader().forEachLine { str ->
|
||||
file.println(transform(str))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) =
|
||||
copyResource(
|
||||
"$dummyFrameworkResource$relativeFrom",
|
||||
outputFramework.get().resolve(relativeTo)
|
||||
)
|
||||
|
||||
private fun copyFrameworkTextFile(
|
||||
relativeFrom: String,
|
||||
relativeTo: String = relativeFrom,
|
||||
transform: (String) -> String = { it }
|
||||
) = copyTextResource(
|
||||
"$dummyFrameworkResource$relativeFrom",
|
||||
outputFramework.get().resolve(relativeTo),
|
||||
transform
|
||||
)
|
||||
|
||||
@TaskAction
|
||||
fun create() {
|
||||
// Reset the destination directory
|
||||
with(outputFramework.get()) {
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
// Copy files for the dummy framework.
|
||||
copyFrameworkFile("Info.plist")
|
||||
copyFrameworkFile("dummy", frameworkName.get())
|
||||
copyFrameworkFile("Headers/placeholder.h")
|
||||
copyFrameworkTextFile("Modules/module.modulemap") {
|
||||
if (it == "framework module dummy {") {
|
||||
it.replace("dummy", frameworkName.get())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a def-file for the given CocoaPods dependency.
|
||||
*/
|
||||
abstract class DefFileTask : DefaultTask() {
|
||||
|
||||
@get:Nested
|
||||
abstract val pod: Property<CocoapodsDependency>
|
||||
|
||||
@get:Input
|
||||
abstract val useLibraries: Property<Boolean>
|
||||
|
||||
@get:OutputFile
|
||||
val outputFile: File
|
||||
get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def")
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
outputFile.parentFile.mkdirs()
|
||||
outputFile.writeText(buildString {
|
||||
appendLine("language = Objective-C")
|
||||
with(pod.get()) {
|
||||
when {
|
||||
headers != null -> appendLine("headers = $headers")
|
||||
useLibraries.get() -> logger.warn(
|
||||
"""
|
||||
w: Pod '$moduleName' should have 'headers' property specified when using 'useLibraries()'.
|
||||
Otherwise code from this pod won't be accessible from Kotlin.
|
||||
""".trimIndent()
|
||||
)
|
||||
else -> {
|
||||
appendLine("modules = $moduleName")
|
||||
|
||||
// Linker opt with framework name is added so produced cinterop klib would have this flag inside its manifest
|
||||
// This way error will be more obvious when someone will try to depend on a library with this cinterop
|
||||
appendLine("linkerOpts = -framework $moduleName")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user