[Gradle, Cocoapods] implement DSL to support Cocoapods Dependencies from Git or Web
Issue #KT-41367 Fixed
This commit is contained in:
committed by
Andrey.Lozhkin
parent
b02f0f0a25
commit
fcf5060907
+562
-113
@@ -6,17 +6,25 @@
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
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.POD_BUILD_DEPENDENCIES_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_IMPORT_TASK_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SETUP_BUILD_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.konan.target.HostManager
|
||||
import org.junit.Assume.assumeFalse
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
class CocoaPodsIT : BaseGradleIT() {
|
||||
@@ -24,14 +32,44 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
override val defaultGradleVersion: GradleVersionRequired
|
||||
get() = GradleVersionRequired.FOR_MPP_SUPPORT
|
||||
|
||||
|
||||
// We use Kotlin DSL. Earlier Gradle versions fail at accessors codegen.
|
||||
val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT
|
||||
private val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT
|
||||
|
||||
val PODFILE_IMPORT_DIRECTIVE_PLACEHOLDER = "<import_mode_directive>"
|
||||
|
||||
val cocoapodsSingleKtPod = "new-mpp-cocoapods-single"
|
||||
val cocoapodsMultipleKtPods = "new-mpp-cocoapods-multiple"
|
||||
private val cocoapodsSingleKtPod = "new-mpp-cocoapods-single"
|
||||
private val cocoapodsMultipleKtPods = "new-mpp-cocoapods-multiple"
|
||||
private val templateProjectName = "new-mpp-cocoapods-template"
|
||||
private val groovyTemplateProjectName = "new-mpp-cocoapods-template-groovy"
|
||||
|
||||
private val dummyTaskName = ":$DUMMY_FRAMEWORK_TASK_NAME"
|
||||
private val podspecTaskName = ":$POD_SPEC_TASK_NAME"
|
||||
private val podDownloadTaskName = ":$POD_DOWNLOAD_TASK_NAME"
|
||||
private val podGenTaskName = ":$POD_GEN_TASK_NAME"
|
||||
private val podBuildTaskName = ":$POD_BUILD_DEPENDENCIES_TASK_NAME"
|
||||
private val podSetupBuildTaskName = ":$POD_SETUP_BUILD_TASK_NAME"
|
||||
private val podImportTaskName = ":$POD_IMPORT_TASK_NAME"
|
||||
private val podInstallTaskName = ":$POD_INSTALL_TASK_NAME"
|
||||
private val cinteropTaskName = ":cinterop"
|
||||
|
||||
private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking"
|
||||
private val defaultPodName = "AFNetworking"
|
||||
private val defaultTarget = "IOS"
|
||||
|
||||
private val defaultPodDownloadTaskName = podDownloadTaskName + defaultPodName
|
||||
private val defaultPodGenTaskName = podGenTaskName + defaultTarget
|
||||
private val defaultBuildTaskName = podBuildTaskName + defaultTarget
|
||||
private val defaultSetupBuildTaskName = podSetupBuildTaskName + defaultTarget
|
||||
private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget
|
||||
|
||||
private lateinit var hooks: CustomHooks
|
||||
private lateinit var project: BaseGradleIT.Project
|
||||
|
||||
@Before
|
||||
fun configure() {
|
||||
hooks = CustomHooks()
|
||||
project = getProjectByName(templateProjectName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodspecSingle() = doTestPodspec(
|
||||
@@ -79,31 +117,20 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testPodImportUseFrameworksSingle() = doTestPodImport(
|
||||
cocoapodsSingleKtPod,
|
||||
"ios-app",
|
||||
ImportMode.FRAMEWORKS,
|
||||
mapOf("kotlin-library" to null)
|
||||
)
|
||||
fun testPodImportUseFrameworksSingle() = doTestImportSingle(ImportMode.FRAMEWORKS)
|
||||
|
||||
@Test
|
||||
fun testPodImportUseModularHeadersSingle() =
|
||||
doTestPodImport(
|
||||
cocoapodsSingleKtPod,
|
||||
"ios-app",
|
||||
ImportMode.MODULAR_HEADERS,
|
||||
mapOf("kotlin-library" to null)
|
||||
)
|
||||
fun testPodImportUseModularHeadersSingle() = doTestImportSingle(ImportMode.MODULAR_HEADERS)
|
||||
|
||||
@Test
|
||||
fun testPodspecMupltiple() = doTestPodspec(
|
||||
fun testPodspecMultiple() = doTestPodspec(
|
||||
cocoapodsMultipleKtPods,
|
||||
mapOf("kotlin-library" to null, "second-library" to null),
|
||||
mapOf("kotlin-library" to kotlinLibraryPodspecContent(), "second-library" to secondLibraryPodspecContent()),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testPodspecCustomFrameworkNameMupltiple() = doTestPodspec(
|
||||
fun testPodspecCustomFrameworkNameMultiple() = doTestPodspec(
|
||||
cocoapodsMultipleKtPods,
|
||||
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary"),
|
||||
mapOf(
|
||||
@@ -113,7 +140,7 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testXcodeUseFrameworksMupltiple() = doTestXcode(
|
||||
fun testXcodeUseFrameworksMultiple() = doTestXcode(
|
||||
cocoapodsMultipleKtPods,
|
||||
ImportMode.FRAMEWORKS,
|
||||
"ios-app",
|
||||
@@ -121,7 +148,7 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testXcodeUseFrameworksWithCustomFrameworkNameMupltiple() = doTestXcode(
|
||||
fun testXcodeUseFrameworksWithCustomFrameworkNameMultiple() = doTestXcode(
|
||||
cocoapodsMultipleKtPods,
|
||||
ImportMode.FRAMEWORKS,
|
||||
"ios-app",
|
||||
@@ -129,7 +156,7 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testXcodeUseModularHeadersMupltiple() = doTestXcode(
|
||||
fun testXcodeUseModularHeadersMultiple() = doTestXcode(
|
||||
cocoapodsMultipleKtPods,
|
||||
ImportMode.MODULAR_HEADERS,
|
||||
"ios-app",
|
||||
@@ -137,7 +164,7 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testXcodeUseModularHeadersWithCustomFrameworkNameMupltiple() = doTestXcode(
|
||||
fun testXcodeUseModularHeadersWithCustomFrameworkNameMultiple() = doTestXcode(
|
||||
cocoapodsMultipleKtPods,
|
||||
ImportMode.MODULAR_HEADERS,
|
||||
"ios-app",
|
||||
@@ -145,47 +172,528 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testPodImportUseFrameworksMupltiple() = doTestPodImport(
|
||||
cocoapodsMultipleKtPods,
|
||||
"ios-app",
|
||||
ImportMode.FRAMEWORKS,
|
||||
mapOf("kotlin-library" to null, "second-library" to null)
|
||||
)
|
||||
fun testPodImportUseFrameworksMultiple() = doTestImportMultiple(ImportMode.FRAMEWORKS)
|
||||
|
||||
@Test
|
||||
fun testPodImportUseModularHeadersMupltiple() =
|
||||
doTestPodImport(
|
||||
cocoapodsMultipleKtPods,
|
||||
"ios-app",
|
||||
ImportMode.MODULAR_HEADERS,
|
||||
mapOf("kotlin-library" to null, "second-library" to null)
|
||||
fun testPodImportUseModularHeadersMultiple() = doTestImportMultiple(ImportMode.MODULAR_HEADERS)
|
||||
|
||||
@Test
|
||||
fun testSpecReposDownloads() {
|
||||
val podName = "example"
|
||||
val podRepo = "https://github.com/alozhkin/spec_repo_example"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(podName)
|
||||
addSpecRepo(podRepo)
|
||||
}
|
||||
project.testDownload(listOf(podRepo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadGitNoTagNorCommit() {
|
||||
doTestGit()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadGitTag() {
|
||||
doTestGit(tag = "4.0.0")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadGitCommit() {
|
||||
doTestGit(commit = "9c07ac0a5645abb58850253eeb109ed0dca515c1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadGitBranch() {
|
||||
doTestGit(branch = "2974")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadGitBranchAndCommit() {
|
||||
val branch = "2974"
|
||||
val commit = "21637dd6164c0641e414bdaf3885af6f1ef15aee"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(defaultPodName, produceGitBlock(defaultPodRepo, branchName = branch, commitName = commit))
|
||||
}
|
||||
hooks.addHook {
|
||||
checkGitRepo(commitName = commit)
|
||||
}
|
||||
project.testDownload(listOf(defaultPodRepo))
|
||||
}
|
||||
|
||||
// tag priority is bigger than branch priority
|
||||
@Test
|
||||
fun testPodDownloadGitBranchAndTag() {
|
||||
val branch = "2974"
|
||||
val tag = "4.0.0"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(defaultPodName, produceGitBlock(defaultPodRepo, branchName = branch, tagName = tag))
|
||||
}
|
||||
hooks.addHook {
|
||||
checkGitRepo(tagName = tag)
|
||||
}
|
||||
project.testDownload(listOf(defaultPodRepo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUrlZip() = doTestPodDownloadUrl("zip")
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUrlTar() = doTestPodDownloadUrl("tar")
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUrlGZ() = doTestPodDownloadUrl("tar.gz")
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUrlBZ2() = doTestPodDownloadUrl("tar.bz2")
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUrlJar() = doTestPodDownloadUrl("jar")
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUrlWrongName() = doTestPodDownloadUrl(fileExtension = "zip", archiveName = "wrongName")
|
||||
|
||||
@Test
|
||||
fun testDownloadAndImport() {
|
||||
val tag = "4.0.0"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(defaultPodName, produceGitBlock(defaultPodRepo, tagName = tag))
|
||||
}
|
||||
hooks.addHook {
|
||||
checkGitRepo(tagName = tag)
|
||||
}
|
||||
project.testImportWithAsserts(listOf(defaultPodRepo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun warnIfDeprecatedPodspecPathIsUsed() {
|
||||
project = getProjectByName(cocoapodsSingleKtPod)
|
||||
hooks.addHook {
|
||||
assertContains("Please use directory with podspec file, not podspec file itself")
|
||||
}
|
||||
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
|
||||
|
||||
@Test
|
||||
fun testDummyUTD() {
|
||||
hooks.addHook {
|
||||
assertTasksExecuted(dummyTaskName)
|
||||
}
|
||||
project.testWithWrapper(dummyTaskName)
|
||||
|
||||
hooks.rewriteHooks {
|
||||
assertTasksUpToDate(dummyTaskName)
|
||||
}
|
||||
project.testWithWrapper(dummyTaskName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodDownloadUTDWithoutPods() {
|
||||
hooks.addHook {
|
||||
assertTasksUpToDate(podDownloadTaskName)
|
||||
}
|
||||
project.testWithWrapper(podDownloadTaskName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun basicUTDTest() {
|
||||
val tasks = listOf(
|
||||
podspecTaskName,
|
||||
defaultPodDownloadTaskName,
|
||||
defaultPodGenTaskName,
|
||||
defaultSetupBuildTaskName,
|
||||
defaultBuildTaskName,
|
||||
defaultCinteropTaskName
|
||||
)
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(defaultPodName, produceGitBlock(defaultPodRepo))
|
||||
}
|
||||
hooks.addHook {
|
||||
assertTasksExecuted(tasks)
|
||||
}
|
||||
project.testImportWithAsserts(listOf(defaultPodRepo))
|
||||
|
||||
hooks.rewriteHooks {
|
||||
assertTasksUpToDate(tasks)
|
||||
}
|
||||
project.testImport(listOf(defaultPodRepo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPodImportCustomFrameworkName() = doTestPodImport(
|
||||
cocoapodsSingleKtPod,
|
||||
"ios-app",
|
||||
ImportMode.FRAMEWORKS,
|
||||
mapOf("kotlin-library" to "foobaz")
|
||||
)
|
||||
fun testSpecReposUTD() {
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod("AFNetworking")
|
||||
}
|
||||
hooks.addHook {
|
||||
assertTasksExecuted(defaultPodGenTaskName)
|
||||
}
|
||||
project.testSynthetic(defaultPodGenTaskName)
|
||||
with(project.gradleBuildScript()) {
|
||||
addSpecRepo("https://github.com/alozhkin/spec_repo_example.git")
|
||||
}
|
||||
project.testSynthetic(defaultPodGenTaskName)
|
||||
hooks.rewriteHooks {
|
||||
assertTasksUpToDate(defaultPodGenTaskName)
|
||||
}
|
||||
project.testSynthetic(defaultPodGenTaskName)
|
||||
}
|
||||
|
||||
|
||||
// groovy tests
|
||||
|
||||
@Test
|
||||
fun testSkippingTasksOnOtherHosts() {
|
||||
assumeFalse(HostManager.hostIsMac)
|
||||
fun testGroovyDownloadAndImport() {
|
||||
val project = getProjectByName(groovyTemplateProjectName)
|
||||
val tag = "4.0.0"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(defaultPodName, produceGitBlock(defaultPodRepo, tagName = tag))
|
||||
}
|
||||
hooks.addHook {
|
||||
checkGitRepo(tagName = tag)
|
||||
}
|
||||
project.testImportWithAsserts(listOf(defaultPodRepo))
|
||||
}
|
||||
|
||||
with(transformProjectWithPluginsDsl(cocoapodsSingleKtPod, gradleVersion)) {
|
||||
val syntheticTasks = arrayOf(
|
||||
":podInstall",
|
||||
":kotlin-library:podGenIOS",
|
||||
":kotlin-library:podSetupBuildIOS",
|
||||
":kotlin-library:podBuildDependenciesIOS",
|
||||
":kotlin-library:podImport"
|
||||
|
||||
// other tests
|
||||
|
||||
@Test
|
||||
fun testDownloadUrlTestSupportDashInNames() {
|
||||
val fileExtension = "tar.gz"
|
||||
val podName = "Pod-with-dashes"
|
||||
val repoPath = "https://github.com/alozhkin/Pod-with-dashes/raw/master"
|
||||
val flatten = true
|
||||
val repo = "$repoPath/$podName.$fileExtension"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(
|
||||
podName,
|
||||
"""source = url("$repo", $flatten)
|
||||
|moduleName = "Pod_with_dashes"
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
hooks.addHook {
|
||||
assertTrue(url().resolve(podName).exists())
|
||||
}
|
||||
project.testImportWithAsserts(listOf(repo))
|
||||
}
|
||||
|
||||
build(*syntheticTasks, "-Pkotlin.native.cocoapods.generate.wrapper=true") {
|
||||
// Check that tasks working with synthetic projects are skipped on non-mac hosts.
|
||||
assertSuccessful()
|
||||
assertTasksSkipped(*syntheticTasks)
|
||||
|
||||
// paths
|
||||
|
||||
private fun CompiledProject.url() = externalSources().resolve("url")
|
||||
|
||||
private fun CompiledProject.git() = externalSources().resolve("git")
|
||||
|
||||
private fun CompiledProject.externalSources() =
|
||||
fileInWorkingDir("build").resolve("cocoapods").resolve("externalSources")
|
||||
|
||||
|
||||
// test configuration phase
|
||||
|
||||
private class CustomHooks() {
|
||||
private val hooks = mutableSetOf<CompiledProject.() -> Unit>()
|
||||
|
||||
fun addHook(hook: CompiledProject.() -> Unit) {
|
||||
hooks.add(hook)
|
||||
}
|
||||
|
||||
fun rewriteHooks(hook: CompiledProject.() -> Unit) {
|
||||
hooks.clear()
|
||||
hooks.add(hook)
|
||||
}
|
||||
|
||||
fun trigger(project: CompiledProject) {
|
||||
hooks.forEach { function ->
|
||||
project.function()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTestImportSingle(importMode: ImportMode) {
|
||||
val project = getProjectByName(cocoapodsSingleKtPod)
|
||||
val subprojects = listOf("kotlin-library")
|
||||
doTestPodImport(project, subprojects, importMode)
|
||||
}
|
||||
|
||||
private fun doTestImportMultiple(importMode: ImportMode) {
|
||||
val project = getProjectByName(cocoapodsMultipleKtPods)
|
||||
val subprojects = listOf("kotlin-library", "second-library")
|
||||
doTestPodImport(project, subprojects, importMode)
|
||||
}
|
||||
|
||||
private fun doTestPodImport(project: BaseGradleIT.Project, subprojects: List<String>, importMode: ImportMode) {
|
||||
with(project) {
|
||||
preparePodfile("ios-app", importMode)
|
||||
}
|
||||
project.testImportWithAsserts()
|
||||
subprojects.forEach {
|
||||
hooks.rewriteHooks {
|
||||
podImportAsserts(it)
|
||||
}
|
||||
project.testSynthetic(":$it:podImport")
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTestGit(
|
||||
repo: String = defaultPodRepo,
|
||||
pod: String = defaultPodName,
|
||||
branch: String? = null,
|
||||
commit: String? = null,
|
||||
tag: String? = null
|
||||
) {
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(pod, produceGitBlock(repo, branch, commit, tag))
|
||||
}
|
||||
hooks.addHook {
|
||||
checkGitRepo(branch, commit, tag, pod)
|
||||
}
|
||||
project.testDownload(listOf(repo))
|
||||
}
|
||||
|
||||
fun doTestPodDownloadUrl(
|
||||
fileExtension: String,
|
||||
podName: String = "podspecWithFilesExample",
|
||||
repoPath: String = "https://github.com/alozhkin/podspecWithFilesExample/raw/master",
|
||||
archiveName: String = podName,
|
||||
flatten: Boolean = false
|
||||
) {
|
||||
val repo = "$repoPath/$archiveName.$fileExtension"
|
||||
with(project.gradleBuildScript()) {
|
||||
addPod(podName, "source = url(\"$repo\", $flatten)")
|
||||
}
|
||||
hooks.addHook {
|
||||
assertTrue(url().resolve(podName).exists())
|
||||
}
|
||||
project.testImportWithAsserts(listOf(repo))
|
||||
}
|
||||
|
||||
private fun Project.testImportWithAsserts(
|
||||
repos: List<String> = listOf(),
|
||||
vararg args: String
|
||||
) {
|
||||
hooks.addHook {
|
||||
podImportAsserts()
|
||||
}
|
||||
testImport(repos, *args)
|
||||
}
|
||||
|
||||
private fun Project.testImport(
|
||||
repos: List<String> = listOf(),
|
||||
vararg args: String
|
||||
) {
|
||||
for (repo in repos) {
|
||||
assumeTrue(isRepoAvailable(repo))
|
||||
}
|
||||
testSynthetic(podImportTaskName, *args)
|
||||
}
|
||||
|
||||
private fun Project.testDownload(
|
||||
repos: List<String>,
|
||||
vararg args: String
|
||||
) {
|
||||
for (repo in repos) {
|
||||
assumeTrue(isRepoAvailable(repo))
|
||||
}
|
||||
test(podDownloadTaskName, *args)
|
||||
}
|
||||
|
||||
private fun Project.testSynthetic(
|
||||
taskName: String,
|
||||
vararg args: String
|
||||
) {
|
||||
assumeTrue(KotlinCocoapodsPlugin.isAvailableToProduceSynthetic)
|
||||
testWithWrapper(taskName, *args)
|
||||
}
|
||||
|
||||
private fun Project.testWithWrapper(
|
||||
taskName: String,
|
||||
vararg args: String
|
||||
) {
|
||||
test(taskName, "-Pkotlin.native.cocoapods.generate.wrapper=true", *args)
|
||||
}
|
||||
|
||||
private fun Project.test(
|
||||
taskName: String,
|
||||
vararg args: String
|
||||
) {
|
||||
// check that test executable
|
||||
assumeTrue(HostManager.hostIsMac)
|
||||
build(taskName, *args) {
|
||||
//base checks
|
||||
assertSuccessful()
|
||||
hooks.trigger(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProjectByName(projectName: String) = transformProjectWithPluginsDsl(projectName, gradleVersion)
|
||||
|
||||
|
||||
// build script configuration phase
|
||||
|
||||
private fun File.addPod(podName: String, configuration: String? = null) {
|
||||
val pod = "pod(\"$podName\")"
|
||||
val podBlock = configuration?.wrap(pod) ?: pod
|
||||
appendToCocoapodsBlock(podBlock)
|
||||
}
|
||||
|
||||
private fun File.addSpecRepo(specRepo: String) = appendToCocoapodsBlock("url(\"$specRepo\")".wrap("specRepos"))
|
||||
|
||||
private fun File.appendToKotlinBlock(str: String) = appendLine(str.wrap("kotlin"))
|
||||
|
||||
private fun File.appendToCocoapodsBlock(str: String) = appendToKotlinBlock(str.wrap("cocoapods"))
|
||||
|
||||
private fun String.wrap(s: String): String = """
|
||||
|$s {
|
||||
| $this
|
||||
|}
|
||||
""".trimMargin()
|
||||
|
||||
private fun File.appendLine(s: String) = appendText("\n$s")
|
||||
|
||||
private fun produceGitBlock(
|
||||
repo: String = defaultPodRepo,
|
||||
branchName: String? = null,
|
||||
commitName: String? = null,
|
||||
tagName: String? = null
|
||||
): String {
|
||||
val branch = if (branchName != null) "branch = \"$branchName\"" else ""
|
||||
val commit = if (commitName != null) "commit = \"$commitName\"" else ""
|
||||
val tag = if (tagName != null) "tag = \"$tagName\"" else ""
|
||||
return """source = git("$repo") {
|
||||
| $branch
|
||||
| $commit
|
||||
| $tag
|
||||
|}
|
||||
""".trimMargin()
|
||||
}
|
||||
|
||||
|
||||
// proposition phase
|
||||
|
||||
private fun CompiledProject.checkGitRepo(
|
||||
branchName: String? = null,
|
||||
commitName: String? = null,
|
||||
tagName: String? = null,
|
||||
aPodDownloadName: String = defaultPodName
|
||||
) {
|
||||
val gitDir = git().resolve(aPodDownloadName)
|
||||
val podspecFile = gitDir.resolve("$aPodDownloadName.podspec")
|
||||
assertTrue(podspecFile.exists())
|
||||
if (tagName != null) {
|
||||
checkTag(gitDir, tagName)
|
||||
}
|
||||
checkPresentCommits(gitDir, commitName)
|
||||
if (branchName != null) {
|
||||
checkBranch(gitDir, branchName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTag(gitDir: File, tagName: String) {
|
||||
runCommand(
|
||||
gitDir,
|
||||
"git", "name-rev",
|
||||
"--tags",
|
||||
"--name-only",
|
||||
"HEAD"
|
||||
) {
|
||||
val (retCode, out, errorMessage) = this
|
||||
assertEquals(0, retCode, errorMessage)
|
||||
assertTrue(out.contains(tagName), errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPresentCommits(gitDir: File, commitName: String?) {
|
||||
runCommand(
|
||||
gitDir,
|
||||
"git", "log", "--pretty=oneline"
|
||||
) {
|
||||
val (retCode, out, _) = this
|
||||
assertEquals(0, retCode)
|
||||
// get rid of '\n' at the end
|
||||
assertEquals(1, out.trimEnd().lines().size)
|
||||
if (commitName != null) {
|
||||
assertEquals(commitName, out.substringBefore(" "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkBranch(gitDir: File, branchName: String) {
|
||||
runCommand(
|
||||
gitDir,
|
||||
"git", "show-branch"
|
||||
) {
|
||||
val (retCode, out, errorMessage) = this
|
||||
assertEquals(0, retCode)
|
||||
// get rid of '\n' at the end
|
||||
assertEquals(1, out.trimEnd().lines().size, errorMessage)
|
||||
assertEquals("[$branchName]", out.substringBefore(" "), errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRepoAvailable(repo: String): Boolean {
|
||||
var responseCode = 0
|
||||
runCommand(
|
||||
File("/"),
|
||||
"curl",
|
||||
"-s",
|
||||
"-o",
|
||||
"/dev/null",
|
||||
"-w",
|
||||
"%{http_code}",
|
||||
"-L",
|
||||
repo,
|
||||
"--retry", "2"
|
||||
) {
|
||||
val (retCode, out, errorMessage) = this
|
||||
assertEquals(0, retCode, errorMessage)
|
||||
responseCode = out.toInt()
|
||||
}
|
||||
return responseCode == 200
|
||||
}
|
||||
|
||||
private fun CompiledProject.podImportAsserts(projectName: String? = null) {
|
||||
|
||||
val buildScriptText = project.gradleBuildScript(projectName).readText()
|
||||
val taskPrefix = projectName?.let { ":$it" } ?: ""
|
||||
val podspec = "podspec"
|
||||
val podInstall = "podInstall"
|
||||
assertSuccessful()
|
||||
|
||||
if ("noPodspec()" in buildScriptText) {
|
||||
assertTasksSkipped("$taskPrefix:$podspec")
|
||||
}
|
||||
|
||||
if ("podfile" in buildScriptText) {
|
||||
assertTasksExecuted("$taskPrefix:$podInstall")
|
||||
} else {
|
||||
assertTasksSkipped("$taskPrefix:$podInstall")
|
||||
}
|
||||
assertTasksRegisteredByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME"))
|
||||
if (buildScriptText.matches("pod\\(.*\\)".toRegex())) {
|
||||
assertTasksExecutedByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME"))
|
||||
}
|
||||
|
||||
with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_DEPENDENCIES_TASK_NAME).map { "$taskPrefix:$it" }) {
|
||||
if (buildScriptText.matches("pod\\(.*\\)".toRegex())) {
|
||||
assertTasksRegisteredByPrefix(this)
|
||||
assertTasksExecutedByPrefix(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,65 +757,6 @@ class CocoaPodsIT : BaseGradleIT() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTestPodImport(
|
||||
projectName: String,
|
||||
iosAppLocation: String,
|
||||
mode: ImportMode,
|
||||
subprojectsToFrameworkNamesMap: Map<String, String?>,
|
||||
) {
|
||||
assumeTrue(HostManager.hostIsMac)
|
||||
assumeTrue(KotlinCocoapodsPlugin.isAvailableToProduceSynthetic)
|
||||
|
||||
val subprojects = subprojectsToFrameworkNamesMap.keys
|
||||
val gradleProject = transformProjectWithPluginsDsl(projectName, gradleVersion)
|
||||
|
||||
subprojectsToFrameworkNamesMap.forEach { subproject, frameworkName ->
|
||||
frameworkName?.let {
|
||||
gradleProject.useCustomFrameworkName(subproject, it, iosAppLocation)
|
||||
}
|
||||
}
|
||||
|
||||
with(gradleProject) {
|
||||
preparePodfile(iosAppLocation, mode)
|
||||
podImportAsserts()
|
||||
subprojects.forEach { podImportAsserts(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun BaseGradleIT.Project.podImportAsserts(subproject: String? = null) {
|
||||
val buildScriptText = gradleBuildScript(subproject).readText()
|
||||
val taskPrefix = subproject?.let { ":$it" } ?: ""
|
||||
val podImport = "podImport"
|
||||
val podspec = "podspec"
|
||||
val podInstall = "podInstall"
|
||||
|
||||
build("$taskPrefix:$podImport", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
|
||||
assertSuccessful()
|
||||
|
||||
if ("noPodspec()" in buildScriptText) {
|
||||
assertTasksSkipped("$taskPrefix:$podspec")
|
||||
}
|
||||
|
||||
if ("podfile" in buildScriptText) {
|
||||
assertTasksExecuted("$taskPrefix:$podInstall")
|
||||
} else {
|
||||
assertTasksSkipped("$taskPrefix:$podInstall")
|
||||
}
|
||||
assertTasksRegisteredByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME"))
|
||||
if (buildScriptText.matches("pod\\(.*\\)".toRegex())) {
|
||||
assertTasksExecutedByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME"))
|
||||
}
|
||||
|
||||
with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_DEPENDENCIES_TASK_NAME).map { "$taskPrefix:$it" }) {
|
||||
if (buildScriptText.matches("pod\\(.*\\)".toRegex())) {
|
||||
assertTasksRegisteredByPrefix(this)
|
||||
assertTasksExecutedByPrefix(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum class ImportMode(val directive: String) {
|
||||
FRAMEWORKS("use_frameworks!"),
|
||||
MODULAR_HEADERS("use_modular_headers!")
|
||||
|
||||
+1
-1
@@ -20,6 +20,6 @@ kotlin {
|
||||
|
||||
cocoapods {
|
||||
noPodspec()
|
||||
podfile = projectDir.resolve("ios-app/Podfile")
|
||||
podfile = project.file("ios-app/Podfile")
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ kotlin {
|
||||
cocoapods {
|
||||
summary = "CocoaPods test library"
|
||||
homepage = "https://github.com/JetBrains/kotlin"
|
||||
pod("pod_dependency", "1.0", projectDir.resolve("../pod_dependency/pod_dependency.podspec"))
|
||||
pod("subspec_dependency/Core", "1.0", projectDir.resolve("../subspec_dependency/subspec_dependency.podspec"))
|
||||
pod("pod_dependency", "1.0", project.file("../pod_dependency"))
|
||||
pod("subspec_dependency/Core", "1.0", project.file("../subspec_dependency"))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,6 +20,6 @@ kotlin {
|
||||
|
||||
cocoapods {
|
||||
noPodspec()
|
||||
podfile = projectDir.resolve("ios-app/Podfile")
|
||||
podfile = project.file("ios-app/Podfile")
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -21,7 +21,8 @@ kotlin {
|
||||
cocoapods {
|
||||
summary = "CocoaPods test library"
|
||||
homepage = "https://github.com/JetBrains/kotlin"
|
||||
pod("pod_dependency", "1.0", projectDir.resolve("../pod_dependency/pod_dependency.podspec"))
|
||||
pod("subspec_dependency/Core", "1.0", projectDir.resolve("../subspec_dependency/subspec_dependency.podspec"))
|
||||
// test warnIfDeprecatedPodspecPathIsUsed depends on deprecated api usage
|
||||
pod("pod_dependency", "1.0", project.file("../pod_dependency/pod_dependency.podspec"))
|
||||
pod("subspec_dependency/Core", "1.0", project.file("../subspec_dependency"))
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
|
||||
id("org.jetbrains.kotlin.native.cocoapods").version("<pluginMarkerVersion>")
|
||||
}
|
||||
|
||||
group = "com.example"
|
||||
version = "1.0"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven { setUrl("https://dl.bintray.com/kotlin/kotlinx.html/") }
|
||||
}
|
||||
|
||||
group = "org.jetbrains.kotlin.sample.native"
|
||||
version = "1.0"
|
||||
|
||||
kotlin {
|
||||
iosX64("iOS")
|
||||
cocoapods {
|
||||
homepage = "https://github.com/JetBrains/kotlin"
|
||||
summary = "CocoaPods test library"
|
||||
ios.deploymentTarget = "13.5"
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
enableFeaturePreview('GRADLE_METADATA')
|
||||
|
||||
rootProject.name = "cocoapods"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package testProject.`new-mpp-cocoapods-template-groovy`.src.iosMain.kotlin
|
||||
|
||||
fun foo() {
|
||||
println("hi!")
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
|
||||
id("org.jetbrains.kotlin.native.cocoapods").version("<pluginMarkerVersion>")
|
||||
}
|
||||
|
||||
group = "com.example"
|
||||
version = "1.0"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven { setUrl("https://dl.bintray.com/kotlin/kotlinx.html/") }
|
||||
}
|
||||
|
||||
group = "org.jetbrains.kotlin.sample.native"
|
||||
version = "1.0"
|
||||
|
||||
kotlin {
|
||||
iosX64("iOS")
|
||||
cocoapods {
|
||||
homepage = "https://github.com/JetBrains/kotlin"
|
||||
summary = "CocoaPods test library"
|
||||
ios.deploymentTarget = "13.5"
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
enableFeaturePreview('GRADLE_METADATA')
|
||||
|
||||
rootProject.name = "cocoapods"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package testProject.`new-mpp-cocoapods-template`.src.iosMain.kotlin
|
||||
|
||||
fun foo() {
|
||||
println("hi!")
|
||||
}
|
||||
+147
-5
@@ -6,11 +6,15 @@
|
||||
@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility
|
||||
package org.jetbrains.kotlin.gradle.plugin.cocoapods
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Named
|
||||
import org.gradle.api.NamedDomainObjectSet
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
open class CocoapodsExtension(private val project: Project) {
|
||||
@get:Input
|
||||
@@ -80,6 +84,9 @@ open class CocoapodsExtension(private val project: Project) {
|
||||
@Input
|
||||
var frameworkName: String = project.name.asValidFrameworkName()
|
||||
|
||||
@get:Nested
|
||||
internal val specRepos = SpecRepos()
|
||||
|
||||
private val _pods = project.container(CocoapodsDependency::class.java)
|
||||
|
||||
// For some reason Gradle doesn't consume the @Nested annotation on NamedDomainObjectContainer.
|
||||
@@ -99,19 +106,140 @@ open class CocoapodsExtension(private val project: Project) {
|
||||
* Add a CocoaPods dependency to the pod built from this project.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun pod(name: String, version: String? = null, podspec: File? = null, moduleName: String = name.asModuleName()) {
|
||||
fun pod(name: String, version: String? = null, path: File? = null, moduleName: String = name.asModuleName()) {
|
||||
// Empty string will lead to an attempt to create two podDownload tasks.
|
||||
// One is original podDownload and second is podDownload + pod.name
|
||||
var podSource = path
|
||||
if (path != null && !path.isDirectory) {
|
||||
project.logger.warn("Please use directory with podspec file, not podspec file itself")
|
||||
podSource = path.parentFile
|
||||
}
|
||||
require(name.isNotEmpty()) { "Please provide not empty pod name to avoid ambiguity" }
|
||||
addToPods(CocoapodsDependency(name, moduleName, version, podSource?.let { Path(it) }))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a CocoaPods dependency to the pod built from this project.
|
||||
*/
|
||||
fun pod(name: String, configure: CocoapodsDependency.() -> Unit) {
|
||||
// Empty string will lead to an attempt to create two podDownload tasks.
|
||||
// One is original podDownload and second is podDownload + pod.name
|
||||
require(name.isNotEmpty()) { "Please provide not empty pod name to avoid ambiguity" }
|
||||
val dependency = CocoapodsDependency(name, name.split("/")[0])
|
||||
dependency.configure()
|
||||
addToPods(dependency)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CocoaPods dependency to the pod built from this project.
|
||||
*/
|
||||
fun pod(name: String, configure: Closure<*>) = pod(name) {
|
||||
ConfigureUtil.configure(configure, this)
|
||||
}
|
||||
|
||||
private fun addToPods(dependency: CocoapodsDependency) {
|
||||
val name = dependency.name
|
||||
check(_pods.findByName(name) == null) { "Project already has a CocoaPods dependency with name $name" }
|
||||
_pods.add(CocoapodsDependency(name, version, podspec, moduleName))
|
||||
_pods.add(dependency)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add spec repositories (note that spec repository is different from usual git repository).
|
||||
* Please refer to <a href="https://guides.cocoapods.org/making/private-cocoapods.html">cocoapods documentation</a>
|
||||
* for additional information.
|
||||
* Default sources (cdn.cocoapods.org) implicitly included.
|
||||
*/
|
||||
fun specRepos(configure: SpecRepos.() -> Unit) = specRepos.configure()
|
||||
|
||||
/**
|
||||
* Add spec repositories (note that spec repository is different from usual git repository).
|
||||
* Please refer to <a href="https://guides.cocoapods.org/making/private-cocoapods.html">cocoapods documentation</a>
|
||||
* for additional information.
|
||||
* Default sources (cdn.cocoapods.org) implicitly included.
|
||||
*/
|
||||
fun specRepos(configure: Closure<*>) = specRepos {
|
||||
ConfigureUtil.configure(configure, this)
|
||||
}
|
||||
|
||||
data class CocoapodsDependency(
|
||||
private val name: String,
|
||||
@get:Optional @get:Input val version: String?,
|
||||
@get:Optional @get:InputFile val podspec: File?,
|
||||
@get:Input val moduleName: String
|
||||
@get:Input var moduleName: String,
|
||||
@get:Optional @get:Input var version: String? = null,
|
||||
@get:Optional @get:Nested var source: PodLocation? = null
|
||||
) : Named {
|
||||
@Input
|
||||
override fun getName(): String = name
|
||||
|
||||
/**
|
||||
* Url to archived (tar, jar, zip) pod folder, that should contain the podspec file and all sources required by it.
|
||||
*
|
||||
* Archive name should match pod name.
|
||||
*
|
||||
* @param url url to tar, jar or zip archive.
|
||||
* @param flatten does archive contains subdirectory that needs to be expanded
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun url(url: String, flatten: Boolean = false): PodLocation = Url(URI(url), flatten)
|
||||
|
||||
/**
|
||||
* Path to local pod
|
||||
*/
|
||||
fun path(podspecDirectory: String): PodLocation = Path(File(podspecDirectory))
|
||||
|
||||
/**
|
||||
* Path to local pod
|
||||
*/
|
||||
fun path(podspecDirectory: File): PodLocation = Path(podspecDirectory)
|
||||
|
||||
/**
|
||||
* Configure pod from git repository. The podspec file is expected to be in the repository root.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun git(url: String, configure: (Git.() -> Unit)? = null): PodLocation {
|
||||
val git = Git(URI(url))
|
||||
if (configure != null) {
|
||||
git.configure()
|
||||
}
|
||||
return git
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure pod from git repository. The podspec file is expected to be in the repository root.
|
||||
*/
|
||||
fun git(url: String, configure: Closure<*>) = git(url) {
|
||||
ConfigureUtil.configure(configure, this)
|
||||
}
|
||||
|
||||
sealed class PodLocation {
|
||||
internal abstract fun getLocalPath(project: Project, podName: String): String
|
||||
|
||||
data class Url(
|
||||
@get:Input val url: URI,
|
||||
@get:Input var flatten: Boolean
|
||||
) : PodLocation() {
|
||||
override fun getLocalPath(project: Project, podName: String): String {
|
||||
return project.cocoapodsBuildDirs.externalSources("url").resolve(podName).absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
data class Path(@get:InputDirectory val dir: File) : PodLocation() {
|
||||
override fun getLocalPath(project: Project, podName: String): String {
|
||||
return dir.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
data class Git(
|
||||
@get:Input val url: URI,
|
||||
@get:Input @get:Optional var branch: String? = null,
|
||||
@get:Input @get:Optional var tag: String? = null,
|
||||
@get:Input @get:Optional var commit: String? = null
|
||||
) : PodLocation() {
|
||||
override fun getLocalPath(project: Project, podName: String): String {
|
||||
return project.cocoapodsBuildDirs.externalSources("git").resolve(podName).absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class PodspecPlatformSettings(
|
||||
@@ -123,6 +251,20 @@ open class CocoapodsExtension(private val project: Project) {
|
||||
override fun getName(): String = name
|
||||
}
|
||||
|
||||
class SpecRepos {
|
||||
@get:Internal
|
||||
internal val specRepos = mutableSetOf<URI>()
|
||||
|
||||
fun url(url: String) {
|
||||
specRepos.add(URI(url))
|
||||
}
|
||||
|
||||
@Input
|
||||
internal fun getAll(): Collection<URI> {
|
||||
return specRepos
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun String.asModuleName() = this
|
||||
.split("/")[0] // Pick the module name from a subspec name.
|
||||
|
||||
+48
-4
@@ -15,10 +15,13 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.addExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
|
||||
import org.jetbrains.kotlin.gradle.targets.native.tasks.*
|
||||
import org.jetbrains.kotlin.gradle.tasks.*
|
||||
import org.jetbrains.kotlin.gradle.tasks.registerTask
|
||||
import org.jetbrains.kotlin.gradle.utils.asValidTaskName
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
@@ -41,11 +44,16 @@ internal class CocoapodsBuildDirs(val project: Project) {
|
||||
val buildSettings: File
|
||||
get() = root.resolve("buildSettings")
|
||||
|
||||
private val synthetic: File
|
||||
val synthetic: File
|
||||
get() = root.resolve("synthetic")
|
||||
|
||||
fun synthetic(kotlinNativeTarget: KotlinNativeTarget) = synthetic.resolve(kotlinNativeTarget.name)
|
||||
|
||||
val externalSources: File
|
||||
get() = root.resolve("externalSources")
|
||||
|
||||
fun externalSources(fileName: String) = externalSources.resolve(fileName)
|
||||
|
||||
fun fatFramework(buildType: String) =
|
||||
root.resolve("fat-frameworks/${buildType.toLowerCase()}")
|
||||
}
|
||||
@@ -61,13 +69,18 @@ private val KotlinNativeTarget.toSetupBuildTaskName: String
|
||||
disambiguationClassifier
|
||||
)
|
||||
|
||||
|
||||
private val KotlinNativeTarget.toBuildDependenciesTaskName: String
|
||||
get() = lowerCamelCaseName(
|
||||
KotlinCocoapodsPlugin.POD_BUILD_DEPENDENCIES_TASK_NAME,
|
||||
disambiguationClassifier
|
||||
)
|
||||
|
||||
private val CocoapodsDependency.toPodDownloadTaskName: String
|
||||
get() = lowerCamelCaseName(
|
||||
KotlinCocoapodsPlugin.POD_DOWNLOAD_TASK_NAME,
|
||||
name
|
||||
)
|
||||
|
||||
open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
|
||||
private fun KotlinMultiplatformExtension.supportedTargets() = targets
|
||||
@@ -186,7 +199,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
) {
|
||||
val moduleNames = mutableSetOf<String>()
|
||||
|
||||
|
||||
cocoapodsExtension.pods.all { pod ->
|
||||
if (moduleNames.contains(pod.moduleName)) {
|
||||
return@all
|
||||
@@ -317,12 +329,41 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerPodDownloadTask(
|
||||
project: Project,
|
||||
cocoapodsExtension: CocoapodsExtension
|
||||
) {
|
||||
val downloadAllTask = project.tasks.register(POD_DOWNLOAD_TASK_NAME) {
|
||||
it.group = TASK_GROUP
|
||||
it.description = "Downloads CocoaPods dependencies from external sources"
|
||||
}
|
||||
|
||||
cocoapodsExtension.pods.all { pod ->
|
||||
val podSource = pod.source
|
||||
val downloadPodTask = when (podSource) {
|
||||
is Git -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadGitTask::class.java) {
|
||||
it.cocoapodsExtension = cocoapodsExtension
|
||||
it.podName = project.provider { pod.name }
|
||||
it.podSource = project.provider { podSource as Git }
|
||||
}
|
||||
is Url -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadUrlTask::class.java) {
|
||||
it.cocoapodsExtension = cocoapodsExtension
|
||||
it.podName = project.provider { pod.name }
|
||||
it.podSource = project.provider { podSource as Url }
|
||||
}
|
||||
else -> return@all
|
||||
}
|
||||
|
||||
downloadAllTask.dependsOn(downloadPodTask)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerPodGenTask(
|
||||
project: Project, kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension
|
||||
) {
|
||||
|
||||
val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java)
|
||||
|
||||
val downloadPods = project.tasks.named(POD_DOWNLOAD_TASK_NAME)
|
||||
kotlinExtension.supportedTargets().all { target ->
|
||||
project.tasks.register(target.toPodGenTaskName, PodGenTask::class.java) {
|
||||
it.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
|
||||
@@ -330,6 +371,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
it.kotlinNativeTarget = target
|
||||
it.cocoapodsExtension = cocoapodsExtension
|
||||
it.dependsOn(podspecTaskProvider)
|
||||
it.dependsOn(downloadPods)
|
||||
it.onlyIf { isAvailableToProduceSynthetic }
|
||||
}
|
||||
}
|
||||
@@ -415,6 +457,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
createSyncTask(project, kotlinExtension)
|
||||
registerPodspecTask(project, cocoapodsExtension)
|
||||
|
||||
registerPodDownloadTask(project, cocoapodsExtension)
|
||||
registerPodGenTask(project, kotlinExtension, cocoapodsExtension)
|
||||
registerPodInstallTask(project, cocoapodsExtension)
|
||||
registerPodSetupBuildTasks(project, kotlinExtension, cocoapodsExtension)
|
||||
@@ -443,6 +486,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
|
||||
const val POD_SPEC_TASK_NAME = "podspec"
|
||||
const val DUMMY_FRAMEWORK_TASK_NAME = "generateDummyFramework"
|
||||
const val POD_INSTALL_TASK_NAME = "podInstall"
|
||||
const val POD_DOWNLOAD_TASK_NAME = "podDownload"
|
||||
const val POD_GEN_TASK_NAME = "podGen"
|
||||
const val POD_SETUP_BUILD_TASK_NAME = "podSetupBuild"
|
||||
const val POD_BUILD_DEPENDENCIES_TASK_NAME = "podBuildDependencies"
|
||||
|
||||
+244
-1
@@ -6,9 +6,14 @@
|
||||
package org.jetbrains.kotlin.gradle.targets.native.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.artifacts.repositories.ArtifactRepository
|
||||
import org.gradle.api.file.FileTree
|
||||
import org.gradle.api.file.RelativePath
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.jetbrains.kotlin.gradle.plugin.cocoapods.*
|
||||
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
|
||||
@@ -18,7 +23,9 @@ import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
internal val KotlinNativeTarget.toBuildSettingsFileName: String
|
||||
get() = "build-settings-$disambiguationClassifier.properties"
|
||||
@@ -91,6 +98,7 @@ open class PodInstallTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class CocoapodsWithSyntheticTask : DefaultTask() {
|
||||
init {
|
||||
onlyIf {
|
||||
@@ -102,6 +110,237 @@ abstract class CocoapodsWithSyntheticTask : DefaultTask() {
|
||||
internal lateinit var cocoapodsExtension: CocoapodsExtension
|
||||
}
|
||||
|
||||
abstract class PodDownloadTask : CocoapodsWithSyntheticTask() {
|
||||
@get:Input
|
||||
internal lateinit var podName: Provider<String>
|
||||
}
|
||||
|
||||
|
||||
open class PodDownloadUrlTask : PodDownloadTask() {
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var podSource: Provider<Url>
|
||||
|
||||
@get:Internal
|
||||
internal val urlDir = project.cocoapodsBuildDirs.externalSources("url")
|
||||
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val podSourceDir = project.provider {
|
||||
urlDir.resolve(podName.get())
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
internal val permittedFileExtensions = listOf("zip", "tar", "tgz", "tbz", "txz", "gzip", "tar.gz", "tar.bz2", "tar.xz", "jar")
|
||||
|
||||
@TaskAction
|
||||
fun download() {
|
||||
val podLocation = podSource.get()
|
||||
val url = podLocation.url.toString()
|
||||
val repoUrl = url.substringBeforeLast("/")
|
||||
val fileName = url.substringAfterLast("/")
|
||||
val fileNameWithoutExtension = fileName.substringBefore(".")
|
||||
val extension = fileName.substringAfter(".")
|
||||
require(permittedFileExtensions.contains(extension)) { "Unknown file extension" }
|
||||
|
||||
val repo = setupRepo(repoUrl)
|
||||
val dependency = createDependency(fileNameWithoutExtension, extension)
|
||||
val configuration = project.configurations.detachedConfiguration(dependency)
|
||||
val artifact = configuration.singleFile
|
||||
copyArtifactToUrlDir(artifact, extension, podLocation.flatten)
|
||||
project.repositories.remove(repo)
|
||||
}
|
||||
|
||||
private fun setupRepo(repoUrl: String): ArtifactRepository {
|
||||
return project.repositories.ivy { repo ->
|
||||
repo.setUrl(repoUrl)
|
||||
repo.patternLayout {
|
||||
it.artifact("[artifact].[ext]")
|
||||
}
|
||||
repo.metadataSources {
|
||||
it.artifact()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDependency(fileNameWithoutExtension: String, extension: String) = project.dependencies.create(
|
||||
mapOf(
|
||||
"name" to fileNameWithoutExtension,
|
||||
"version" to "1.0",
|
||||
"ext" to extension
|
||||
)
|
||||
)
|
||||
|
||||
private fun copyArtifactToUrlDir(artifact: File, extension: String, flatten: Boolean) {
|
||||
val archiveTree = archiveTree(artifact.absolutePath, extension)
|
||||
project.copy {
|
||||
val destinationDir = podSourceDir.get()
|
||||
it.into(destinationDir)
|
||||
it.from(archiveTree)
|
||||
if (extension == "jar") {
|
||||
it.exclude("META-INF/")
|
||||
}
|
||||
if (!flatten) {
|
||||
it.eachFile { file ->
|
||||
file.relativePath = RelativePath(true, *file.relativePath.segments.drop(1).toTypedArray())
|
||||
}
|
||||
it.includeEmptyDirs = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun archiveTree(path: String, extension: String): FileTree {
|
||||
return if (extension == "zip" || extension == "jar") {
|
||||
project.zipTree(path)
|
||||
} else {
|
||||
project.tarTree(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class PodDownloadGitTask : PodDownloadTask() {
|
||||
|
||||
@get:Nested
|
||||
internal lateinit var podSource: Provider<Git>
|
||||
|
||||
@get:Internal
|
||||
internal val gitDir = project.cocoapodsBuildDirs.externalSources("git")
|
||||
|
||||
@get:OutputDirectory
|
||||
internal val repo = project.provider {
|
||||
gitDir.resolve(podName.get())
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun download() {
|
||||
repo.get().deleteRecursively()
|
||||
val git = podSource.get()
|
||||
val branch = git.tag ?: git.branch
|
||||
val commit = git.commit
|
||||
val url = git.url
|
||||
try {
|
||||
when {
|
||||
commit != null -> {
|
||||
retrieveCommit(url, commit)
|
||||
}
|
||||
branch != null -> {
|
||||
cloneShallow(url, branch)
|
||||
}
|
||||
else -> {
|
||||
cloneHead(git)
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
fallback(git)
|
||||
}
|
||||
}
|
||||
|
||||
private fun retrieveCommit(url: URI, commit: String) {
|
||||
val initCommand = listOf(
|
||||
"git",
|
||||
"init"
|
||||
)
|
||||
val repo = repo.get()
|
||||
repo.mkdir()
|
||||
val configProcess: ProcessBuilder.() -> Unit = { directory(repo) }
|
||||
runCommand(initCommand, configProcess)
|
||||
|
||||
val fetchCommand = listOf(
|
||||
"git",
|
||||
"fetch",
|
||||
"--depth", "1",
|
||||
"$url",
|
||||
commit
|
||||
)
|
||||
runCommand(fetchCommand, configProcess)
|
||||
|
||||
val checkoutCommand = listOf(
|
||||
"git",
|
||||
"checkout",
|
||||
"FETCH_HEAD"
|
||||
)
|
||||
runCommand(checkoutCommand, configProcess)
|
||||
}
|
||||
|
||||
private fun cloneShallow(url: URI, branch: String) {
|
||||
val shallowCloneCommand = listOf(
|
||||
"git",
|
||||
"clone",
|
||||
"$url",
|
||||
podName.get(),
|
||||
"--branch", branch,
|
||||
"--depth", "1"
|
||||
)
|
||||
val configProcess: ProcessBuilder.() -> Unit = { directory(gitDir) }
|
||||
runCommand(shallowCloneCommand, configProcess)
|
||||
}
|
||||
|
||||
private fun cloneHead(podspecLocation: Git) {
|
||||
val cloneHeadCommand = listOf(
|
||||
"git",
|
||||
"clone",
|
||||
"${podspecLocation.url}",
|
||||
podName.get(),
|
||||
"--depth", "1"
|
||||
)
|
||||
val configProcess: ProcessBuilder.() -> Unit = { directory(gitDir) }
|
||||
runCommand(cloneHeadCommand, configProcess)
|
||||
}
|
||||
|
||||
private fun fallback(podspecLocation: Git) {
|
||||
// removing any traces of other commands
|
||||
gitDir.resolve(podName.get()).deleteRecursively()
|
||||
val cloneAllCommand = listOf(
|
||||
"git",
|
||||
"clone",
|
||||
"${podspecLocation.url}",
|
||||
podName.get()
|
||||
)
|
||||
val configProcess: ProcessBuilder.() -> Unit = { directory(gitDir) }
|
||||
runCommand(cloneAllCommand, configProcess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommand(
|
||||
command: List<String>,
|
||||
processConfiguration: ((ProcessBuilder.() -> Unit))? = null,
|
||||
errorHandler: ((retCode: Int, process: Process) -> Unit)? = null
|
||||
): String {
|
||||
val process = ProcessBuilder(command)
|
||||
.apply {
|
||||
if (processConfiguration != null) {
|
||||
this.processConfiguration()
|
||||
}
|
||||
}.start()
|
||||
|
||||
var inputText = ""
|
||||
var errorText = ""
|
||||
|
||||
val inputThread = thread {
|
||||
inputText = process.inputStream.use {
|
||||
it.reader().readText()
|
||||
}
|
||||
}
|
||||
|
||||
val errorThread = thread {
|
||||
errorText = process.errorStream.use {
|
||||
it.reader().readText()
|
||||
}
|
||||
}
|
||||
|
||||
inputThread.join()
|
||||
errorThread.join()
|
||||
|
||||
val retCode = process.waitFor()
|
||||
check(retCode == 0) {
|
||||
errorHandler?.invoke(retCode, process)
|
||||
?: "Executing of '${command.joinToString(" ")}' failed with code $retCode and message: $errorText"
|
||||
}
|
||||
|
||||
return inputText
|
||||
}
|
||||
|
||||
/**
|
||||
* The task takes the path to the .podspec file and calls `pod gen`
|
||||
* to create synthetic xcode project and workspace.
|
||||
@@ -126,13 +365,17 @@ open class PodGenTask : CocoapodsWithSyntheticTask() {
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val syntheticDir = project.cocoapodsBuildDirs.synthetic(kotlinNativeTarget).apply { mkdirs() }
|
||||
val localPodspecPaths = cocoapodsExtension.pods.mapNotNull { it.podspec?.parentFile?.absolutePath }
|
||||
val localPodspecPaths = cocoapodsExtension.pods.mapNotNull { it.source?.getLocalPath(project, it.name) }
|
||||
|
||||
val sources = cocoapodsExtension.specRepos.getAll().toMutableList()
|
||||
sources += URI("https://cdn.cocoapods.org")
|
||||
|
||||
val podGenProcessArgs = listOfNotNull(
|
||||
"pod", "gen",
|
||||
"--platforms=${kotlinNativeTarget.platformLiteral}",
|
||||
"--gen-directory=${syntheticDir.absolutePath}",
|
||||
localPodspecPaths.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--local-sources=$it" },
|
||||
sources.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--sources=$it" },
|
||||
podspecProvider.get().absolutePath
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user