[Gradle, Cocoapods] refactor I/O, improve Up-To-Date

all synthetic tasks are per pod
tasks receive parts of extension as inputs
This commit is contained in:
Andrey.Lozhkin
2020-08-28 19:33:36 +03:00
parent 1888462dc0
commit ef57c62576
4 changed files with 637 additions and 229 deletions
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.DUMMY_FRAMEWORK_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.DUMMY_FRAMEWORK_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_BUILD_DEPENDENCIES_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_BUILD_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_DOWNLOAD_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_DOWNLOAD_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_GEN_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_GEN_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME
@@ -16,10 +16,8 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue import org.junit.Assume.assumeTrue
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -46,7 +44,7 @@ class CocoaPodsIT : BaseGradleIT() {
private val podspecTaskName = ":$POD_SPEC_TASK_NAME" private val podspecTaskName = ":$POD_SPEC_TASK_NAME"
private val podDownloadTaskName = ":$POD_DOWNLOAD_TASK_NAME" private val podDownloadTaskName = ":$POD_DOWNLOAD_TASK_NAME"
private val podGenTaskName = ":$POD_GEN_TASK_NAME" private val podGenTaskName = ":$POD_GEN_TASK_NAME"
private val podBuildTaskName = ":$POD_BUILD_DEPENDENCIES_TASK_NAME" private val podBuildTaskName = ":$POD_BUILD_TASK_NAME"
private val podSetupBuildTaskName = ":$POD_SETUP_BUILD_TASK_NAME" private val podSetupBuildTaskName = ":$POD_SETUP_BUILD_TASK_NAME"
private val podImportTaskName = ":$POD_IMPORT_TASK_NAME" private val podImportTaskName = ":$POD_IMPORT_TASK_NAME"
private val podInstallTaskName = ":$POD_INSTALL_TASK_NAME" private val podInstallTaskName = ":$POD_INSTALL_TASK_NAME"
@@ -54,14 +52,30 @@ class CocoaPodsIT : BaseGradleIT() {
private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking" private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking"
private val defaultPodName = "AFNetworking" private val defaultPodName = "AFNetworking"
private val downloadUrlPodName = "podspecWithFilesExample"
private val downloadUrlRepoName = "https://github.com/alozhkin/podspecWithFilesExample/raw/master"
private val defaultTarget = "IOS" private val defaultTarget = "IOS"
private val defaultFamily = "IOS" private val defaultFamily = "IOS"
private val defaultSDK = "iphonesimulator" private val defaultSDK = "iphonesimulator"
private val defaultPodDownloadTaskName = podDownloadTaskName + defaultPodName private val defaultPodDownloadTaskName = podDownloadFullTaskName()
private val defaultPodGenTaskName = podGenTaskName + defaultFamily private val defaultPodGenTaskName = podGenFullTaskName()
private val defaultBuildTaskName = podBuildTaskName + defaultSDK.capitalize() private val defaultBuildTaskName = podBuildFullTaskName()
private val defaultSetupBuildTaskName = podSetupBuildTaskName + defaultSDK.capitalize() private val defaultSetupBuildTaskName = podSetupBuildFullTaskName()
private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget
private val downloadUrlTaskName = podDownloadTaskName + downloadUrlPodName.capitalize()
private fun podDownloadFullTaskName(podName: String = defaultPodName) = podDownloadTaskName + podName.capitalize()
private fun podGenFullTaskName(familyName: String = defaultFamily) = podGenTaskName + familyName.capitalize()
private fun podSetupBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) =
podSetupBuildTaskName + podName.capitalize() + sdkName.capitalize()
private fun podBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) =
podBuildTaskName + podName.capitalize() + sdkName.capitalize()
private fun cinteropFullTaskName(podName: String = defaultPodName, targetName: String = defaultTarget) =
cinteropTaskName + podName.capitalize() + targetName.capitalize()
private lateinit var hooks: CustomHooks private lateinit var hooks: CustomHooks
private lateinit var project: BaseGradleIT.Project private lateinit var project: BaseGradleIT.Project
@@ -118,10 +132,10 @@ class CocoaPodsIT : BaseGradleIT() {
) )
@Test @Test
fun testPodImportUseFrameworksSingle() = doTestImportSingle(ImportMode.FRAMEWORKS) fun testPodImportSingle() = doTestImportSingle()
@Test @Test
fun testPodImportUseModularHeadersSingle() = doTestImportSingle(ImportMode.MODULAR_HEADERS) fun testPodImportMultiple() = doTestImportMultiple()
@Test @Test
fun testPodspecMultiple() = doTestPodspec( fun testPodspecMultiple() = doTestPodspec(
@@ -173,20 +187,14 @@ class CocoaPodsIT : BaseGradleIT() {
) )
@Test @Test
fun testPodImportUseFrameworksMultiple() = doTestImportMultiple(ImportMode.FRAMEWORKS) fun testSpecReposImport() {
@Test
fun testPodImportUseModularHeadersMultiple() = doTestImportMultiple(ImportMode.MODULAR_HEADERS)
@Test
fun testSpecReposDownloads() {
val podName = "example" val podName = "example"
val podRepo = "https://github.com/alozhkin/spec_repo_example" val podRepo = "https://github.com/alozhkin/spec_repo"
with(project.gradleBuildScript()) { with(project.gradleBuildScript()) {
addPod(podName) addPod(podName)
addSpecRepo(podRepo) addSpecRepo(podRepo)
} }
project.testDownload(listOf(podRepo)) project.testImportWithAsserts(listOf(podRepo))
} }
@Test @Test
@@ -275,19 +283,6 @@ class CocoaPodsIT : BaseGradleIT() {
project.test(":kotlin-library:podDownload") project.test(":kotlin-library:podDownload")
} }
@Ignore
@Test
fun testSubspecWithModuleName() {
with(project.gradleBuildScript()) {
addPod("AFNetworking/Reachability", "moduleName = \"CustomName\"")
addPod("AFNetworking/Security")
}
hooks.addHook {
assertTasksExecuted(defaultCinteropTaskName, cinteropTaskName + "AFNetworking/Security" + defaultTarget)
}
project.testImportWithAsserts()
}
// up-to-date tests // up-to-date tests
@@ -355,6 +350,282 @@ class CocoaPodsIT : BaseGradleIT() {
project.testSynthetic(defaultPodGenTaskName) project.testSynthetic(defaultPodGenTaskName)
} }
@Test
fun testUTDPodAdded() {
with(project.gradleBuildScript()) {
addPod(defaultPodName, produceGitBlock(defaultPodRepo))
}
project.testImport(listOf(defaultPodRepo))
val anotherPodName = "Alamofire"
val anotherPodRepo = "https://github.com/Alamofire/Alamofire"
with(project.gradleBuildScript()) {
addPod(anotherPodName, produceGitBlock(anotherPodRepo))
}
hooks.rewriteHooks {
assertTasksExecuted(
podspecTaskName,
podDownloadFullTaskName(anotherPodName),
defaultPodGenTaskName,
podSetupBuildFullTaskName(anotherPodName),
podBuildFullTaskName(anotherPodName),
cinteropFullTaskName(anotherPodName)
)
assertTasksUpToDate(
defaultPodDownloadTaskName,
defaultSetupBuildTaskName,
defaultBuildTaskName,
defaultCinteropTaskName
)
}
project.testImport(listOf(defaultPodRepo, anotherPodRepo))
with(project.gradleBuildScript()) {
removePod(anotherPodName)
}
hooks.rewriteHooks {
assertTasksNotRegisteredByPrefix(
listOf(
podDownloadFullTaskName(anotherPodName),
podBuildFullTaskName(anotherPodName),
cinteropFullTaskName(anotherPodName)
)
)
assertTasksUpToDate(
defaultBuildTaskName,
defaultPodDownloadTaskName,
defaultSetupBuildTaskName,
defaultCinteropTaskName
)
}
project.testImport(listOf(defaultPodRepo))
}
@Test
fun testImportSubspecs() {
with(project.gradleBuildScript()) {
addPod("SDWebImage/Core")
addPod("SDWebImage/MapKit")
}
project.testImport(listOf(defaultPodRepo))
}
@Test
fun testUTDTargetAdded() {
with(project.gradleBuildScript()) {
addPod(defaultPodName, produceGitBlock(defaultPodRepo))
appendToCocoapodsBlock("osx.deploymentTarget = \"13.5\"")
}
project.testImport(listOf(defaultPodRepo))
val anotherTarget = "MacosX64"
val anotherSdk = "macosx"
val anotherFamily = "OSX"
with(project.gradleBuildScript()) {
appendToKotlinBlock(anotherTarget.decapitalize() + "()")
}
hooks.rewriteHooks {
assertTasksExecuted(
podGenFullTaskName(anotherFamily),
podSetupBuildFullTaskName(sdkName = anotherSdk),
podBuildFullTaskName(sdkName = anotherSdk),
cinteropFullTaskName(targetName = anotherTarget)
)
assertTasksUpToDate(
podspecTaskName,
defaultPodDownloadTaskName,
defaultPodGenTaskName,
defaultSetupBuildTaskName,
defaultBuildTaskName,
defaultCinteropTaskName
)
}
project.testImport(listOf(defaultPodRepo))
with(project.gradleBuildScript()) {
var text = readText()
text = text.replace(anotherTarget.decapitalize() + "()", "")
writeText(text)
}
hooks.rewriteHooks {
assertTasksNotRegisteredByPrefix(
listOf(
podGenFullTaskName(anotherFamily),
podSetupBuildFullTaskName(sdkName = anotherSdk),
podBuildFullTaskName(sdkName = anotherSdk),
cinteropFullTaskName(targetName = anotherTarget)
)
)
assertTasksUpToDate(
podspecTaskName,
defaultPodDownloadTaskName,
defaultPodGenTaskName,
defaultSetupBuildTaskName,
defaultBuildTaskName,
defaultCinteropTaskName
)
}
project.testImport(listOf(defaultPodRepo))
}
@Test
fun testUTDPodspec() {
project.testWithWrapper(podspecTaskName)
hooks.addHook {
assertTasksExecuted(podspecTaskName)
}
with(project.gradleBuildScript()) {
appendToCocoapodsBlock("license = \"new license name\"")
}
project.testWithWrapper(podspecTaskName)
with(project.gradleBuildScript()) {
appendToCocoapodsBlock("license = \"new license name\"")
}
hooks.rewriteHooks {
assertTasksUpToDate(podspecTaskName)
}
project.testWithWrapper(podspecTaskName)
}
@Test
fun testUTDPodspecDeploymentTarget() {
project.testWithWrapper(podspecTaskName)
hooks.addHook {
assertTasksExecuted(podspecTaskName)
}
with(project.gradleBuildScript()) {
appendToCocoapodsBlock("ios.deploymentTarget = \"12.5\"")
}
project.testWithWrapper(podspecTaskName)
hooks.rewriteHooks {
assertTasksUpToDate(podspecTaskName)
}
project.testWithWrapper(podspecTaskName)
}
@Test
fun testUTDDownload() {
val gitRepo = downloadUrlRepoName.substringBeforeLast("/").substringBeforeLast("/")
with(project.gradleBuildScript()) {
addPod(downloadUrlPodName, produceGitBlock(repo = gitRepo))
}
hooks.addHook {
assertTasksExecuted(downloadUrlTaskName)
}
project.testDownload(listOf(gitRepo))
with(project.gradleBuildScript()) {
changePod(downloadUrlPodName, produceGitBlock(repo = gitRepo, commitName = "f52f035018b4f3fe253d50ce85a7e0652a62ee9b"))
}
project.testDownload(listOf(gitRepo))
hooks.rewriteHooks {
assertTasksUpToDate(downloadUrlTaskName)
}
project.testDownload(listOf(gitRepo))
val podArchivePath = "$downloadUrlRepoName/$downloadUrlPodName.tar.gz"
with(project.gradleBuildScript()) {
changePod(downloadUrlPodName, "source = url(\"$podArchivePath\")")
}
hooks.rewriteHooks {
assertTasksExecuted(downloadUrlTaskName)
}
project.testDownload(listOf(podArchivePath))
}
@Test
fun testUTDPodGen() {
with(project.gradleBuildScript()) {
addPod(defaultPodName)
}
val repos = listOf(
"https://github.com/alozhkin/spec_repo_example",
"https://github.com/alozhkin/spec_repo_example_2"
)
for (repo in repos) {
assumeTrue(isRepoAvailable(repo))
}
hooks.addHook {
assertTasksExecuted(defaultPodGenTaskName)
}
project.testSynthetic(defaultPodGenTaskName)
with(project.gradleBuildScript()) {
addSpecRepo("https://github.com/alozhkin/spec_repo_example")
}
project.testSynthetic(defaultPodGenTaskName)
with(project.gradleBuildScript()) {
addSpecRepo("https://github.com/alozhkin/spec_repo_example_2")
}
project.testSynthetic(defaultPodGenTaskName)
hooks.rewriteHooks {
assertTasksUpToDate(defaultPodGenTaskName)
}
project.testSynthetic(defaultPodGenTaskName)
}
@Test
fun testUTDBuild() {
with(project.gradleBuildScript()) {
addPod(defaultPodName, produceGitBlock())
}
hooks.addHook {
assertTasksExecuted(defaultBuildTaskName)
}
project.testImport()
val repo = "$downloadUrlRepoName/$downloadUrlPodName.tar.gz"
with(project.gradleBuildScript()) {
addPod(downloadUrlPodName, "source = url(\"$repo\")")
}
val urlTaskName = podBuildFullTaskName(downloadUrlPodName)
hooks.rewriteHooks {
assertTasksUpToDate(defaultBuildTaskName)
assertTasksExecuted(urlTaskName)
}
project.testImport()
val anotherTarget = "MacosX64"
val anotherSdk = "macosx"
with(project.gradleBuildScript()) {
appendToCocoapodsBlock("osx.deploymentTarget = \"13.5\"")
appendToKotlinBlock(anotherTarget.decapitalize() + "()")
}
val anotherSdkDefaultPodTaskName = podBuildFullTaskName(sdkName = anotherSdk)
val anotherTargetUrlTaskName = podBuildFullTaskName(downloadUrlPodName, anotherSdk)
hooks.rewriteHooks {
assertTasksUpToDate(defaultBuildTaskName, urlTaskName)
assertTasksExecuted(anotherSdkDefaultPodTaskName, anotherTargetUrlTaskName)
}
project.testImport()
hooks.rewriteHooks {
assertTasksUpToDate(defaultBuildTaskName, urlTaskName, anotherSdkDefaultPodTaskName, anotherTargetUrlTaskName)
}
project.testImport()
}
@Test
fun testPodBuildUTDClean() {
with(project.gradleBuildScript()) {
addPod(defaultPodName, produceGitBlock())
}
hooks.addHook {
assertTasksExecuted(defaultBuildTaskName)
}
project.testImport()
hooks.rewriteHooks {}
project.test(":clean")
hooks.addHook {
assertTasksExecuted(defaultBuildTaskName)
}
project.testImport()
}
@Test
fun testPodInstallWithoutPodFile() {
project.testSynthetic(podInstallTaskName)
}
// groovy tests // groovy tests
@@ -395,6 +666,13 @@ class CocoaPodsIT : BaseGradleIT() {
project.testImportWithAsserts(listOf(repo)) project.testImportWithAsserts(listOf(repo))
} }
@Test
fun supportPodsWithDependencies() {
with(project.gradleBuildScript()) {
addPod("AlamofireImage")
}
project.testImportWithAsserts()
}
// paths // paths
@@ -408,7 +686,7 @@ class CocoaPodsIT : BaseGradleIT() {
// test configuration phase // test configuration phase
private class CustomHooks() { private class CustomHooks {
private val hooks = mutableSetOf<CompiledProject.() -> Unit>() private val hooks = mutableSetOf<CompiledProject.() -> Unit>()
fun addHook(hook: CompiledProject.() -> Unit) { fun addHook(hook: CompiledProject.() -> Unit) {
@@ -427,21 +705,21 @@ class CocoaPodsIT : BaseGradleIT() {
} }
} }
private fun doTestImportSingle(importMode: ImportMode) { private fun doTestImportSingle() {
val project = getProjectByName(cocoapodsSingleKtPod) val project = getProjectByName(cocoapodsSingleKtPod)
val subprojects = listOf("kotlin-library") val subprojects = listOf("kotlin-library")
doTestPodImport(project, subprojects, importMode) doTestPodImport(project, subprojects)
} }
private fun doTestImportMultiple(importMode: ImportMode) { private fun doTestImportMultiple() {
val project = getProjectByName(cocoapodsMultipleKtPods) val project = getProjectByName(cocoapodsMultipleKtPods)
val subprojects = listOf("kotlin-library", "second-library") val subprojects = listOf("kotlin-library", "second-library")
doTestPodImport(project, subprojects, importMode) doTestPodImport(project, subprojects)
} }
private fun doTestPodImport(project: BaseGradleIT.Project, subprojects: List<String>, importMode: ImportMode) { private fun doTestPodImport(project: BaseGradleIT.Project, subprojects: List<String>) {
with(project) { with(project) {
preparePodfile("ios-app", importMode) preparePodfile("ios-app", ImportMode.FRAMEWORKS)
} }
project.testImportWithAsserts() project.testImportWithAsserts()
subprojects.forEach { subprojects.forEach {
@@ -468,10 +746,10 @@ class CocoaPodsIT : BaseGradleIT() {
project.testDownload(listOf(repo)) project.testDownload(listOf(repo))
} }
fun doTestPodDownloadUrl( private fun doTestPodDownloadUrl(
fileExtension: String, fileExtension: String,
podName: String = "podspecWithFilesExample", podName: String = downloadUrlPodName,
repoPath: String = "https://github.com/alozhkin/podspecWithFilesExample/raw/master", repoPath: String = downloadUrlRepoName,
archiveName: String = podName, archiveName: String = podName,
flatten: Boolean = false flatten: Boolean = false
) { ) {
@@ -554,6 +832,30 @@ class CocoaPodsIT : BaseGradleIT() {
appendToCocoapodsBlock(podBlock) appendToCocoapodsBlock(podBlock)
} }
private fun File.removePod(podName: String) {
val text = readText()
val begin = text.indexOf("""pod("$podName")""")
require(begin != -1) { "Pod doesn't exist in file" }
var index = begin + """pod("$podName")""".length - 1
if (text.indexOf("""pod("$podName") {""", startIndex = begin) != -1) {
index += 2
var bracket = 1
while (bracket != 0) {
if (text[++index] == '{') {
bracket++
} else if (text[index] == '}') {
bracket--
}
}
}
writeText(text.removeRange(begin..index))
}
private fun File.changePod(podName: String, newConfiguration: String? = null) {
removePod(podName)
addPod(podName, newConfiguration)
}
private fun File.addSpecRepo(specRepo: String) = appendToCocoapodsBlock("url(\"$specRepo\")".wrap("specRepos")) private fun File.addSpecRepo(specRepo: String) = appendToCocoapodsBlock("url(\"$specRepo\")".wrap("specRepos"))
private fun File.appendToKotlinBlock(str: String) = appendLine(str.wrap("kotlin")) private fun File.appendToKotlinBlock(str: String) = appendLine(str.wrap("kotlin"))
@@ -691,7 +993,7 @@ class CocoaPodsIT : BaseGradleIT() {
assertTasksExecutedByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME")) assertTasksExecutedByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME"))
} }
with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_DEPENDENCIES_TASK_NAME).map { "$taskPrefix:$it" }) { with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_TASK_NAME).map { "$taskPrefix:$it" }) {
if (buildScriptText.matches("pod\\(.*\\)".toRegex())) { if (buildScriptText.matches("pod\\(.*\\)".toRegex())) {
assertTasksRegisteredByPrefix(this) assertTasksRegisteredByPrefix(this)
assertTasksExecutedByPrefix(this) assertTasksExecutedByPrefix(this)
@@ -11,6 +11,7 @@ import org.gradle.api.Project
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Sync import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.daemon.common.trimQuotes
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.KonanTarget.* import org.jetbrains.kotlin.konan.target.KonanTarget.*
import java.io.File import java.io.File
import java.util.*
internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this) get() = CocoapodsBuildDirs(this)
@@ -68,17 +70,17 @@ private val Family.toPodGenTaskName: String
name name
) )
private val String.toSetupBuildTaskName: String private fun String.toSetupBuildTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName(
get() = lowerCamelCaseName( KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME,
KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME, pod.schemeName,
this this
) )
private val String.toBuildDependenciesTaskName: String private fun String.toBuildDependenciesTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName(
get() = lowerCamelCaseName( KotlinCocoapodsPlugin.POD_BUILD_TASK_NAME,
KotlinCocoapodsPlugin.POD_BUILD_DEPENDENCIES_TASK_NAME, pod.schemeName,
this this
) )
private val CocoapodsDependency.toPodDownloadTaskName: String private val CocoapodsDependency.toPodDownloadTaskName: String
get() = lowerCamelCaseName( get() = lowerCamelCaseName(
@@ -225,7 +227,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val defTask = project.registerTask<DefFileTask>( val defTask = project.registerTask<DefFileTask>(
lowerCamelCaseName("generateDef", pod.moduleName).asValidTaskName() lowerCamelCaseName("generateDef", pod.moduleName).asValidTaskName()
) { ) {
it.pod = pod it.pod = project.provider { pod }
it.description = "Generates a def file for CocoaPods dependencies with module ${pod.moduleName}" it.description = "Generates a def file for CocoaPods dependencies with module ${pod.moduleName}"
// This task is an implementation detail so we don't add it in any group // This task is an implementation detail so we don't add it in any group
// to avoid showing it in the `tasks` output. // to avoid showing it in the `tasks` output.
@@ -248,8 +250,8 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
&& project.findProperty(CONFIGURATION_PROPERTY) == null && project.findProperty(CONFIGURATION_PROPERTY) == null
) { ) {
val podBuildTaskProvider = val podBuildTaskProvider =
project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName, PodBuildTask::class.java) project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java)
interopTask.inputs.file(podBuildTaskProvider.get().buildSettingsFileProvider) interopTask.inputs.file(podBuildTaskProvider.get().buildSettingsFile)
interopTask.dependsOn(podBuildTaskProvider) interopTask.dependsOn(podBuildTaskProvider)
} }
@@ -275,9 +277,9 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
&& project.findProperty(CONFIGURATION_PROPERTY) == null && project.findProperty(CONFIGURATION_PROPERTY) == null
) { ) {
val podBuildTaskProvider = val podBuildTaskProvider =
project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName, PodBuildTask::class.java) project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java)
val buildSettings = val buildSettings =
podBuildTaskProvider.get().buildSettingsFileProvider.get() podBuildTaskProvider.get().buildSettingsFile.get()
.inputStream() .inputStream()
.use { .use {
PodBuildSettingsProperties.readSettingsFromStream(it) PodBuildSettingsProperties.readSettingsFromStream(it)
@@ -291,9 +293,14 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
buildSettings.headerPaths?.let { args -> buildSettings.headerPaths?.let { args ->
interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-I$it" }) interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-I$it" })
} }
buildSettings.frameworkPaths?.let { args ->
interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-F$it" }) val frameworkPaths = buildSettings.frameworkPaths
} val configurationBuildDir = buildSettings.configurationBuildDir
val frameworkPathsSelfIncluding = mutableListOf<String>()
frameworkPathsSelfIncluding += configurationBuildDir.trimQuotes()
frameworkPaths?.let { frameworkPathsSelfIncluding.addAll(it.splitQuotedArgs()) }
interop.compilerOpts.addAll(frameworkPathsSelfIncluding.map { "-F$it" })
} }
} }
} }
@@ -306,7 +313,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
cocoapodsExtension: CocoapodsExtension cocoapodsExtension: CocoapodsExtension
) { ) {
project.tasks.register(DUMMY_FRAMEWORK_TASK_NAME, DummyFrameworkTask::class.java) { project.tasks.register(DUMMY_FRAMEWORK_TASK_NAME, DummyFrameworkTask::class.java) {
it.cocoapodsExtension = cocoapodsExtension it.frameworkName = project.provider { cocoapodsExtension.frameworkName }
} }
} }
@@ -319,7 +326,18 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) { project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) {
it.group = TASK_GROUP it.group = TASK_GROUP
it.description = "Generates a podspec file for CocoaPods import" it.description = "Generates a podspec file for CocoaPods import"
it.cocoapodsExtension = cocoapodsExtension it.needPodspec = project.provider { cocoapodsExtension.needPodspec }
it.pods.set(cocoapodsExtension.pods)
it.version = project.provider { cocoapodsExtension.version }
it.homepage.set(cocoapodsExtension.homepage)
it.license.set(cocoapodsExtension.license)
it.authors.set(cocoapodsExtension.authors)
it.summary.set(cocoapodsExtension.summary)
it.frameworkName = project.provider { cocoapodsExtension.frameworkName }
it.ios = project.provider { cocoapodsExtension.ios }
it.osx = project.provider { cocoapodsExtension.osx }
it.tvos = project.provider { cocoapodsExtension.tvos }
it.watchos = project.provider { cocoapodsExtension.watchos }
it.dependsOn(dummyFrameworkTaskProvider) it.dependsOn(dummyFrameworkTaskProvider)
val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
if (generateWrapper) { if (generateWrapper) {
@@ -335,13 +353,13 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project.tasks.register(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) { project.tasks.register(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) {
it.group = TASK_GROUP it.group = TASK_GROUP
it.description = "Invokes `pod install` call within Podfile location directory" it.description = "Invokes `pod install` call within Podfile location directory"
it.cocoapodsExtension = cocoapodsExtension it.podfile.set(cocoapodsExtension.podfile)
it.onlyIf { isAvailableToProduceSynthetic } it.onlyIf { isAvailableToProduceSynthetic }
//TODO avoid subproject task management here //TODO avoid subproject task management here
project.allprojects.map { it.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) } project.allprojects.map { it.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) }
.forEach { podspecTaskProvider -> .forEach { podspecTaskProvider ->
podspecTaskProvider.get().takeIf { task -> task.cocoapodsExtension.needPodspec } podspecTaskProvider.get().takeIf { task -> task.needPodspec.get() }
?.also { task -> it.inputs.file(task.outputFileProvider) } ?.also { task -> it.inputs.file(task.outputFileProvider) }
it.dependsOn(podspecTaskProvider) it.dependsOn(podspecTaskProvider)
} }
@@ -361,12 +379,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val podSource = pod.source val podSource = pod.source
val downloadPodTask = when (podSource) { val downloadPodTask = when (podSource) {
is Git -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadGitTask::class.java) { is Git -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadGitTask::class.java) {
it.cocoapodsExtension = cocoapodsExtension
it.podName = project.provider { pod.name } it.podName = project.provider { pod.name }
it.podSource = project.provider { podSource as Git } it.podSource = project.provider { podSource as Git }
} }
is Url -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadUrlTask::class.java) { is Url -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadUrlTask::class.java) {
it.cocoapodsExtension = cocoapodsExtension
it.podName = project.provider { pod.name } it.podName = project.provider { pod.name }
it.podSource = project.provider { podSource as Url } it.podSource = project.provider { podSource as Url }
} }
@@ -394,10 +410,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project.tasks.register(family.toPodGenTaskName, PodGenTask::class.java) { project.tasks.register(family.toPodGenTaskName, PodGenTask::class.java) {
it.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" it.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
it.podspecProvider = podspecTaskProvider.get().outputFileProvider it.podspec = podspecTaskProvider.map { task -> task.outputFileProvider.get() }
it.specRepos = project.provider { cocoapodsExtension.specRepos }
it.family = family it.family = family
it.cocoapodsExtension = cocoapodsExtension it.pods.set(cocoapodsExtension.pods)
it.dependsOn(podspecTaskProvider)
it.dependsOn(downloadPods) it.dependsOn(downloadPods)
it.onlyIf { isAvailableToProduceSynthetic } it.onlyIf { isAvailableToProduceSynthetic }
} }
@@ -409,30 +425,36 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
kotlinExtension: KotlinMultiplatformExtension, kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension cocoapodsExtension: CocoapodsExtension
) { ) {
val sdks = mutableSetOf<String>() val schemeNames = mutableSetOf<String>()
kotlinExtension.supportedTargets().all { target -> cocoapodsExtension.pods.all { pod ->
val sdk = target.toValidSDK if (schemeNames.contains(pod.schemeName)) {
if (sdk in sdks) {
return@all return@all
} }
sdks += sdk schemeNames.add(pod.schemeName)
val podGenTaskProvider = project.tasks.named(target.konanTarget.family.toPodGenTaskName, PodGenTask::class.java) val sdks = mutableSetOf<String>()
project.tasks.register(sdk.toSetupBuildTaskName, PodSetupBuildTask::class.java) { kotlinExtension.supportedTargets().all loop@{ target ->
it.group = TASK_GROUP
it.description = "Collect environment variables from .xcworkspace file"
it.cocoapodsExtension = cocoapodsExtension
it.sdk = project.provider { sdk }
val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) val sdk = target.toValidSDK
it.schemeName = podspecTaskProvider.map { it.specName } if (sdk in sdks) {
return@loop
}
sdks += sdk
it.podsXcodeProjDirProvider = podGenTaskProvider.get().podsXcodeProjDirProvider val podGenTaskProvider = project.tasks.named(target.konanTarget.family.toPodGenTaskName, PodGenTask::class.java)
it.dependsOn(podGenTaskProvider) project.tasks.register(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java) {
it.onlyIf { isAvailableToProduceSynthetic } it.group = TASK_GROUP
it.description = "Collect environment variables from .xcworkspace file"
it.pod = project.provider { pod }
it.sdk = project.provider { sdk }
it.podsXcodeProjDir = podGenTaskProvider.map { podGen -> podGen.podsXcodeProjDir.get() }
it.frameworkName = project.provider { cocoapodsExtension.frameworkName }
it.dependsOn(podGenTaskProvider)
it.onlyIf { isAvailableToProduceSynthetic }
}
} }
} }
} }
@@ -442,27 +464,38 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
kotlinExtension: KotlinMultiplatformExtension, kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension cocoapodsExtension: CocoapodsExtension
) { ) {
val sdks = mutableSetOf<String>() val schemeNames = mutableSetOf<String>()
kotlinExtension.supportedTargets().all { target -> cocoapodsExtension.pods.all { pod ->
val sdk = target.toValidSDK if (schemeNames.contains(pod.schemeName)) {
if (sdk in sdks) {
return@all return@all
} }
sdks += sdk schemeNames.add(pod.schemeName)
val podSetupBuildTaskProvider = project.tasks.named(sdk.toSetupBuildTaskName, PodSetupBuildTask::class.java) val sdks = mutableSetOf<String>()
project.tasks.register(sdk.toBuildDependenciesTaskName, PodBuildTask::class.java) { kotlinExtension.supportedTargets().all loop@{ target ->
it.group = TASK_GROUP
it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme" val sdk = target.toValidSDK
it.sdk = project.provider { sdk }
it.cocoapodsExtension = cocoapodsExtension if (sdk in sdks) {
it.podsXcodeProjDirProvider = podSetupBuildTaskProvider.get().podsXcodeProjDirProvider return@loop
it.buildSettingsFileProvider = podSetupBuildTaskProvider.get().buildSettingsFileProvider }
it.dependsOn(podSetupBuildTaskProvider) sdks += sdk
it.onlyIf { isAvailableToProduceSynthetic }
val podSetupBuildTaskProvider =
project.tasks.named(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java)
project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) {
it.group = TASK_GROUP
it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme"
it.sdk = project.provider { sdk }
it.pod = project.provider { pod }
it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() }
it.buildSettingsFile = podSetupBuildTaskProvider.map { task -> task.buildSettingsFile.get() }
it.onlyIf { isAvailableToProduceSynthetic }
}
} }
} }
} }
@@ -471,7 +504,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project: Project, project: Project,
kotlinExtension: KotlinMultiplatformExtension kotlinExtension: KotlinMultiplatformExtension
) { ) {
val podInstallTaskProvider = project.tasks.named(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) val podInstallTaskProvider = project.tasks.named(POD_INSTALL_TASK_NAME, PodInstallTask::class.java)
project.tasks.register(POD_IMPORT_TASK_NAME) { project.tasks.register(POD_IMPORT_TASK_NAME) {
it.group = TASK_GROUP it.group = TASK_GROUP
@@ -531,7 +563,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
const val POD_DOWNLOAD_TASK_NAME = "podDownload" const val POD_DOWNLOAD_TASK_NAME = "podDownload"
const val POD_GEN_TASK_NAME = "podGen" const val POD_GEN_TASK_NAME = "podGen"
const val POD_SETUP_BUILD_TASK_NAME = "podSetupBuild" const val POD_SETUP_BUILD_TASK_NAME = "podSetupBuild"
const val POD_BUILD_DEPENDENCIES_TASK_NAME = "podBuildDependencies" const val POD_BUILD_TASK_NAME = "podBuild"
const val POD_IMPORT_TASK_NAME = "podImport" const val POD_IMPORT_TASK_NAME = "podImport"
// We don't move these properties in PropertiesProvider because // We don't move these properties in PropertiesProvider because
@@ -7,15 +7,14 @@ package org.jetbrains.kotlin.gradle.targets.native.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.repositories.ArtifactRepository import org.gradle.api.artifacts.repositories.ArtifactRepository
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree import org.gradle.api.file.FileTree
import org.gradle.api.file.RelativePath import org.gradle.api.file.RelativePath
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.Optional import org.gradle.api.tasks.Optional
import org.jetbrains.kotlin.gradle.plugin.cocoapods.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.Family
import java.io.File import java.io.File
@@ -34,6 +33,9 @@ private val Family.platformLiteral: String
else -> throw IllegalArgumentException("Bad family ${this.name}") else -> throw IllegalArgumentException("Bad family ${this.name}")
} }
val CocoapodsDependency.schemeName: String
get() = name.split("/")[0]
/** /**
* The task takes the path to the Podfile and calls `pod install` * The task takes the path to the Podfile and calls `pod install`
* to obtain sources or artifacts for the declared dependencies. * to obtain sources or artifacts for the declared dependencies.
@@ -41,24 +43,24 @@ private val Family.platformLiteral: String
*/ */
open class PodInstallTask : DefaultTask() { open class PodInstallTask : DefaultTask() {
init { init {
onlyIf { cocoapodsExtension?.podfile != null } onlyIf { podfile.isPresent }
} }
@get:Optional @get:Optional
@get:Nested @get:Input
internal var cocoapodsExtension: CocoapodsExtension? = null internal val podfile = project.objects.property(File::class.java)
@get:Optional @get:Optional
@get:OutputDirectory @get:OutputDirectory
internal val podsXcodeProjDirProvider: Provider<File>? internal val podsXcodeProjDirProvider: Provider<File>?
get() = cocoapodsExtension?.podfile?.let { get() = podfile.orNull?.let {
project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") } project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") }
} }
@TaskAction @TaskAction
fun doPodInstall() { fun doPodInstall() {
cocoapodsExtension?.podfile?.parentFile?.also { podfileDir -> podfile.orNull?.parentFile?.also { podfileDir ->
val podInstallProcess = ProcessBuilder("pod", "install").apply { val podInstallProcess = ProcessBuilder("pod", "install").apply {
directory(podfileDir) directory(podfileDir)
}.start() }.start()
@@ -81,25 +83,12 @@ open class PodInstallTask : DefaultTask() {
} }
} }
abstract class DownloadCocoapodsTask : DefaultTask() {
abstract class CocoapodsWithSyntheticTask : DefaultTask() {
init {
onlyIf {
cocoapodsExtension.pods.isNotEmpty()
}
}
@get:Nested
internal lateinit var cocoapodsExtension: CocoapodsExtension
}
abstract class PodDownloadTask : CocoapodsWithSyntheticTask() {
@get:Input @get:Input
internal lateinit var podName: Provider<String> internal lateinit var podName: Provider<String>
} }
open class PodDownloadUrlTask : DownloadCocoapodsTask() {
open class PodDownloadUrlTask : PodDownloadTask() {
@get:Nested @get:Nested
internal lateinit var podSource: Provider<Url> internal lateinit var podSource: Provider<Url>
@@ -181,8 +170,7 @@ open class PodDownloadUrlTask : PodDownloadTask() {
} }
} }
open class PodDownloadGitTask : DownloadCocoapodsTask() {
open class PodDownloadGitTask : PodDownloadTask() {
@get:Nested @get:Nested
internal lateinit var podSource: Provider<Git> internal lateinit var podSource: Provider<Git>
@@ -328,19 +316,31 @@ private fun runCommand(
* The task takes the path to the .podspec file and calls `pod gen` * The task takes the path to the .podspec file and calls `pod gen`
* to create synthetic xcode project and workspace. * to create synthetic xcode project and workspace.
*/ */
open class PodGenTask : CocoapodsWithSyntheticTask() { open class PodGenTask : DefaultTask() {
init {
onlyIf {
pods.get().isNotEmpty()
}
}
@get:InputFile @get:InputFile
internal lateinit var podspecProvider: Provider<File> internal lateinit var podspec: Provider<File>
@get:Internal @get:Internal
lateinit var family: Family lateinit var family: Family
@get:Nested
internal lateinit var specRepos: Provider<SpecRepos>
@get:Nested
val pods = project.objects.listProperty(CocoapodsDependency::class.java)
@get:OutputDirectory @get:OutputDirectory
internal val podsXcodeProjDirProvider: Provider<File> internal val podsXcodeProjDir: Provider<File>
get() = project.provider { get() = project.provider {
project.cocoapodsBuildDirs.synthetic(family) project.cocoapodsBuildDirs.synthetic(family)
.resolve(podspecProvider.get().nameWithoutExtension) .resolve(podspec.get().nameWithoutExtension)
.resolve("Pods") .resolve("Pods")
.resolve("Pods.xcodeproj") .resolve("Pods.xcodeproj")
} }
@@ -348,18 +348,18 @@ open class PodGenTask : CocoapodsWithSyntheticTask() {
@TaskAction @TaskAction
fun generate() { fun generate() {
val syntheticDir = project.cocoapodsBuildDirs.synthetic(family).apply { mkdirs() } val syntheticDir = project.cocoapodsBuildDirs.synthetic(family).apply { mkdirs() }
val localPodspecPaths = cocoapodsExtension.pods.mapNotNull { it.source?.getLocalPath(project, it.name) } val localPodspecPaths = pods.get().mapNotNull { it.source?.getLocalPath(project, it.name) }
val sources = cocoapodsExtension.specRepos.getAll().toMutableList() val specRepos = specRepos.get().getAll().toMutableList()
sources += URI("https://cdn.cocoapods.org") specRepos += URI("https://cdn.cocoapods.org")
val podGenProcessArgs = listOfNotNull( val podGenProcessArgs = listOfNotNull(
"pod", "gen", "pod", "gen",
"--platforms=${family.platformLiteral}", "--platforms=${family.platformLiteral}",
"--gen-directory=${syntheticDir.absolutePath}", "--gen-directory=${syntheticDir.absolutePath}",
localPodspecPaths.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--local-sources=$it" }, localPodspecPaths.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--local-sources=$it" },
sources.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--sources=$it" }, specRepos.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--sources=$it" },
podspecProvider.get().absolutePath podspec.get().absolutePath
) )
val podGenProcess = ProcessBuilder(podGenProcessArgs).apply { val podGenProcess = ProcessBuilder(podGenProcessArgs).apply {
@@ -388,7 +388,7 @@ open class PodGenTask : CocoapodsWithSyntheticTask() {
).joinToString("\n") ).joinToString("\n")
} }
val podsXcprojFile = podsXcodeProjDirProvider.get() val podsXcprojFile = podsXcodeProjDir.get()
check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) { check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) {
"The directory '${podsXcprojFile.path}' was not created as a result of the `pod gen` call." "The directory '${podsXcprojFile.path}' was not created as a result of the `pod gen` call."
} }
@@ -396,35 +396,35 @@ open class PodGenTask : CocoapodsWithSyntheticTask() {
} }
open class PodSetupBuildTask : CocoapodsWithSyntheticTask() { open class PodSetupBuildTask : DefaultTask() {
@get:InputDirectory @get:Input
internal lateinit var podsXcodeProjDirProvider: Provider<File> lateinit var frameworkName: Provider<String>
@get:Input @get:Input
internal lateinit var sdk: Provider<String> internal lateinit var sdk: Provider<String>
@Input @get:Nested
lateinit var schemeName: Provider<String> lateinit var pod: Provider<CocoapodsDependency>
@get:OutputFile @get:OutputFile
internal val buildSettingsFileProvider: Provider<File> = project.provider { internal val buildSettingsFile: Provider<File> = project.provider {
project.cocoapodsBuildDirs project.cocoapodsBuildDirs
.buildSettings .buildSettings
.resolve(sdk.get().toBuildSettingsFileName) .resolve(getBuildSettingFileName(pod.get(), sdk.get()))
} }
private val String.toBuildSettingsFileName: String @get:Internal
get() = "build-settings-$this.properties" internal lateinit var podsXcodeProjDir: Provider<File>
@TaskAction @TaskAction
fun setupBuild() { fun setupBuild() {
val podsXcodeProjDir = podsXcodeProjDirProvider.get() val podsXcodeProjDir = podsXcodeProjDir.get()
val buildSettingsReceivingCommand = listOf( val buildSettingsReceivingCommand = listOf(
"xcodebuild", "-showBuildSettings", "xcodebuild", "-showBuildSettings",
"-project", podsXcodeProjDir.name, "-project", podsXcodeProjDir.name,
"-scheme", schemeName.get(), "-scheme", pod.get().schemeName,
"-sdk", sdk.get() "-sdk", sdk.get()
) )
@@ -442,113 +442,151 @@ open class PodSetupBuildTask : CocoapodsWithSyntheticTask() {
} }
val stdOut = buildSettingsProcess.inputStream val stdOut = buildSettingsProcess.inputStream
val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromStream(stdOut) val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromStream(stdOut)
buildSettingsFileProvider.get().let { buildSettingsProperties.writeSettings(it) } buildSettingsFile.get().let {
buildSettingsProperties.writeSettings(it)
}
} }
} }
private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String =
"build-settings-$sdk-${pod.schemeName}.properties"
/** /**
* The task compiles external cocoa pods sources. * The task compiles external cocoa pods sources.
*/ */
open class PodBuildTask : CocoapodsWithSyntheticTask() { open class PodBuildTask : DefaultTask() {
@get:InputDirectory
internal lateinit var podsXcodeProjDirProvider: Provider<File>
@get:InputFile @get:InputFile
internal lateinit var buildSettingsFileProvider: Provider<File> internal lateinit var buildSettingsFile: Provider<File>
@get:Nested
internal lateinit var pod: Provider<CocoapodsDependency>
@get:InputFiles
internal val srcDir: FileTree
get() = project.fileTree(
buildSettingsFile.map {
PodBuildSettingsProperties.readSettingsFromStream(
FileInputStream(it)
).podsTargetSrcRoot
}
)
@get:Internal
internal var buildDir: Provider<File> = project.provider {
project.file(
PodBuildSettingsProperties.readSettingsFromStream(
FileInputStream(buildSettingsFile.get())
).buildDir
)
}
@get:Input @get:Input
internal lateinit var sdk: Provider<String> internal lateinit var sdk: Provider<String>
@get:Optional @get:OutputFiles
@get:OutputDirectory internal val buildResult: Provider<FileCollection>? = project.provider {
internal var buildDirProvider: Provider<File>? = null project.fileTree(buildDir.get()) {
it.include("**/${pod.get().schemeName}.*/")
it.include("**/${pod.get().schemeName}/")
}
}
private val CocoapodsExtension.CocoapodsDependency.schemeName: String @get:Internal
get() = name.split("/")[0] internal lateinit var podsXcodeProjDir: Provider<File>
@TaskAction @TaskAction
fun buildDependencies() { fun buildDependencies() {
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromStream( val podBuildSettings = PodBuildSettingsProperties.readSettingsFromStream(
FileInputStream(buildSettingsFileProvider.get()) FileInputStream(buildSettingsFile.get())
) )
val podsXcodeProjDir = podsXcodeProjDirProvider.get() val podsXcodeProjDir = podsXcodeProjDir.get()
cocoapodsExtension.pods.all { val podXcodeBuildCommand = listOf(
"xcodebuild",
"-project", podsXcodeProjDir.name,
"-scheme", pod.get().schemeName,
"-sdk", sdk.get(),
"-configuration", podBuildSettings.configuration
)
val podXcodeBuildCommand = listOf( val podBuildProcess = ProcessBuilder(podXcodeBuildCommand)
"xcodebuild", .apply {
"-project", podsXcodeProjDir.name, directory(podsXcodeProjDir.parentFile)
"-scheme", it.schemeName, inheritIO()
"-sdk", sdk.get(), }.start()
"-configuration", podBuildSettings.configuration
)
val podBuildProcess = ProcessBuilder(podXcodeBuildCommand) val podBuildRetCode = podBuildProcess.waitFor()
.apply { check(podBuildRetCode == 0) {
directory(podsXcodeProjDir.parentFile) listOf(
inheritIO() "Executing of '${podXcodeBuildCommand.joinToString(" ")}' failed with code $podBuildRetCode and message:",
}.start() podBuildProcess.errorStream.use { it.reader().readText() }
).joinToString("\n")
val podBuildRetCode = podBuildProcess.waitFor()
check(podBuildRetCode == 0) {
listOf(
"Executing of '${podXcodeBuildCommand.joinToString(" ")}' failed with code $podBuildRetCode and message:",
podBuildProcess.errorStream.use { it.reader().readText() }
).joinToString("\n")
}
} }
buildDirProvider = project.provider { project.file(podBuildSettings.buildDir) }
} }
} }
internal data class PodBuildSettingsProperties( internal data class PodBuildSettingsProperties(
internal val buildDir: String, internal val buildDir: String,
internal val configuration: String, internal val configuration: String,
internal val configurationBuildDir: String,
internal val podsTargetSrcRoot: String,
internal val cflags: String? = null, internal val cflags: String? = null,
internal val headerPaths: String? = null, internal val headerPaths: String? = null,
internal val frameworkPaths: String? = null internal val frameworkPaths: String? = null
) { ) {
fun writeSettings(buildSettingsFile: File) { fun writeSettings(
buildSettingsFile: File
) {
buildSettingsFile.parentFile.mkdirs() buildSettingsFile.parentFile.mkdirs()
buildSettingsFile.delete()
buildSettingsFile.createNewFile() buildSettingsFile.createNewFile()
check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
with(Properties()) { with(buildSettingsFile) {
setProperty(BUILD_DIR, buildDir) appendText("$BUILD_DIR=$buildDir\n")
setProperty(CONFIGURATION, configuration) appendText("$CONFIGURATION=$configuration\n")
cflags?.let { setProperty(OTHER_CFLAGS, it) } appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n")
headerPaths?.let { setProperty(HEADER_SEARCH_PATHS, it) } appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n")
frameworkPaths?.let { setProperty(FRAMEWORK_SEARCH_PATHS, it) } cflags?.let { appendText("$OTHER_CFLAGS=$it\n") }
buildSettingsFile.outputStream().use { headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") }
store(it, null) frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") }
}
} }
} }
companion object { companion object {
const val BUILD_DIR: String = "BUILD_DIR" const val BUILD_DIR = "BUILD_DIR"
const val CONFIGURATION: String = "CONFIGURATION" const val CONFIGURATION = "CONFIGURATION"
const val OTHER_CFLAGS: String = "OTHER_CFLAGS" const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR"
const val HEADER_SEARCH_PATHS: String = "HEADER_SEARCH_PATHS" const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT"
const val FRAMEWORK_SEARCH_PATHS: String = "FRAMEWORK_SEARCH_PATHS" const val OTHER_CFLAGS = "OTHER_CFLAGS"
const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS"
const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS"
fun readSettingsFromStream(inputStream: InputStream): PodBuildSettingsProperties { fun readSettingsFromStream(inputStream: InputStream): PodBuildSettingsProperties {
with(Properties()) { with(Properties()) {
load(inputStream) load(inputStream)
return PodBuildSettingsProperties( return PodBuildSettingsProperties(
getProperty(BUILD_DIR), readProperty(BUILD_DIR),
getProperty(CONFIGURATION), readProperty(CONFIGURATION),
getProperty(OTHER_CFLAGS), readProperty(CONFIGURATION_BUILD_DIR),
getProperty(HEADER_SEARCH_PATHS), readProperty(PODS_TARGET_SRCROOT),
getProperty(FRAMEWORK_SEARCH_PATHS) readNullableProperty(OTHER_CFLAGS),
readNullableProperty(HEADER_SEARCH_PATHS),
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)
} }
} }
@@ -15,6 +15,7 @@ import org.gradle.api.tasks.wrapper.Wrapper
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull 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.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin 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.COCOAPODS_EXTENSION_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY
@@ -38,18 +39,55 @@ open class PodspecTask : DefaultTask() {
internal val outputFileProvider: Provider<File> internal val outputFileProvider: Provider<File>
get() = project.provider { project.file("$specName.podspec") } get() = project.provider { project.file("$specName.podspec") }
@get:Input
internal lateinit var needPodspec: Provider<Boolean>
@get:Nested @get:Nested
internal lateinit var cocoapodsExtension: CocoapodsExtension val pods = project.objects.listProperty(CocoapodsDependency::class.java)
@get:Input
internal lateinit var version: Provider<String>
@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
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 { init {
onlyIf { cocoapodsExtension.needPodspec } onlyIf { needPodspec.get() }
} }
@TaskAction @TaskAction
fun generate() { fun generate() {
val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFileProvider.get().parentFile).path val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFileProvider.get().parentFile).path
val dependencies = cocoapodsExtension.pods.map { pod -> val dependencies = pods.get().map { pod ->
val versionSuffix = if (pod.version != null) ", '${pod.version}'" else "" val versionSuffix = if (pod.version != null) ", '${pod.version}'" else ""
"| spec.dependency '${pod.name}'$versionSuffix" "| spec.dependency '${pod.name}'$versionSuffix"
}.joinToString(separator = "\n") }.joinToString(separator = "\n")
@@ -69,10 +107,8 @@ open class PodspecTask : DefaultTask() {
val syncTask = "${project.path}:$SYNC_TASK_NAME" val syncTask = "${project.path}:$SYNC_TASK_NAME"
val deploymentTargets = run { val deploymentTargets = run {
with(cocoapodsExtension) { listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") {
listOf(ios, osx, tvos, watchos).filter { it.deploymentTarget != null }.joinToString("\n") { "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'"
"| spec.${it.name}.deployment_target = '${it.deploymentTarget}'"
}
} }
} }
@@ -81,15 +117,15 @@ open class PodspecTask : DefaultTask() {
""" """
|Pod::Spec.new do |spec| |Pod::Spec.new do |spec|
| spec.name = '$specName' | spec.name = '$specName'
| spec.version = '${cocoapodsExtension.version}' | spec.version = '${version.get()}'
| spec.homepage = '${cocoapodsExtension.homepage.orEmpty()}' | spec.homepage = '${homepage.getOrEmpty()}'
| spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
| spec.authors = '${cocoapodsExtension.authors.orEmpty()}' | spec.authors = '${authors.getOrEmpty()}'
| spec.license = '${cocoapodsExtension.license.orEmpty()}' | spec.license = '${license.getOrEmpty()}'
| spec.summary = '${cocoapodsExtension.summary.orEmpty()}' | spec.summary = '${summary.getOrEmpty()}'
| |
| spec.static_framework = true | spec.static_framework = true
| spec.vendored_frameworks = "$frameworkDir/${cocoapodsExtension.frameworkName}.framework" | spec.vendored_frameworks = "$frameworkDir/${frameworkName.get()}.framework"
| spec.libraries = "c++" | spec.libraries = "c++"
| spec.module_name = "#{spec.name}_umbrella" | spec.module_name = "#{spec.name}_umbrella"
| |
@@ -143,6 +179,8 @@ open class PodspecTask : DefaultTask() {
} }
} }
fun Provider<String>.getOrEmpty() = getOrElse("")
companion object { companion object {
private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension? private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension?
get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension
@@ -167,17 +205,15 @@ open class PodspecTask : DefaultTask() {
* and then replace it with the real one during a real build process. * and then replace it with the real one during a real build process.
*/ */
open class DummyFrameworkTask : DefaultTask() { open class DummyFrameworkTask : DefaultTask() {
@OutputDirectory @OutputDirectory
val destinationDir = project.cocoapodsBuildDirs.framework val destinationDir = project.cocoapodsBuildDirs.framework
@Input @Input
val frameworkNameProvider: Provider<String> = project.provider { cocoapodsExtension.frameworkName } lateinit var frameworkName: Provider<String>
@get:Nested
internal lateinit var cocoapodsExtension: CocoapodsExtension
private val frameworkDir: File private val frameworkDir: File
get() = destinationDir.resolve("${frameworkNameProvider.get()}.framework") get() = destinationDir.resolve("${frameworkName.get()}.framework")
private fun copyResource(from: String, to: File) { private fun copyResource(from: String, to: File) {
to.parentFile.mkdirs() to.parentFile.mkdirs()
@@ -225,11 +261,11 @@ open class DummyFrameworkTask : DefaultTask() {
// Copy files for the dummy framework. // Copy files for the dummy framework.
copyFrameworkFile("Info.plist") copyFrameworkFile("Info.plist")
copyFrameworkFile("dummy", frameworkNameProvider.get()) copyFrameworkFile("dummy", frameworkName.get())
copyFrameworkFile("Headers/placeholder.h") copyFrameworkFile("Headers/placeholder.h")
copyFrameworkTextFile("Modules/module.modulemap") { copyFrameworkTextFile("Modules/module.modulemap") {
if (it == "framework module dummy {") { if (it == "framework module dummy {") {
it.replace("dummy", frameworkNameProvider.get()) it.replace("dummy", frameworkName.get())
} else { } else {
it it
} }
@@ -242,12 +278,12 @@ open class DummyFrameworkTask : DefaultTask() {
*/ */
open class DefFileTask : DefaultTask() { open class DefFileTask : DefaultTask() {
@Nested @get:Nested
lateinit var pod: CocoapodsExtension.CocoapodsDependency lateinit var pod: Provider<CocoapodsDependency>
@get:OutputFile @get:OutputFile
val outputFile: File val outputFile: File
get() = project.cocoapodsBuildDirs.defs.resolve("${pod.moduleName}.def") get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def")
@TaskAction @TaskAction
fun generate() { fun generate() {
@@ -255,7 +291,7 @@ open class DefFileTask : DefaultTask() {
outputFile.writeText( outputFile.writeText(
""" """
language = Objective-C language = Objective-C
modules = ${pod.moduleName} modules = ${pod.get().moduleName}
""".trimIndent() """.trimIndent()
) )
} }