Coverage Summary for Class: GhostPlugin (com.ghost.gradle)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GhostPlugin |
90.9%
(10/11)
|
63.9%
(23/36)
|
88.1%
(59/67)
|
92.6%
(410/443)
|
| GhostPlugin$apply$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(7/7)
|
| GhostPlugin$apply$2 |
100%
(1/1)
|
|
100%
(2/2)
|
100%
(15/15)
|
| GhostPlugin$apply$3$1 |
100%
(1/1)
|
50%
(1/2)
|
100%
(4/4)
|
100%
(23/23)
|
| GhostPlugin$apply$4 |
100%
(1/1)
|
91.7%
(11/12)
|
100%
(7/7)
|
100%
(77/77)
|
| GhostPlugin$apply$configureKspDependencies$1 |
100%
(1/1)
|
66.7%
(4/6)
|
100%
(6/6)
|
97.1%
(68/70)
|
| GhostPlugin$apply$configureKspDependencies$compilerDep$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| GhostPlugin$Companion |
|
| GhostPlugin$setupAndroidOrJvmCore$apiDep$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| GhostPlugin$setupAndroidOrJvmCore$runtimeDep$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| GhostPlugin$setupKmp$apiDep$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| GhostPlugin$setupKmp$runtimeDep$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| Total |
95.2%
(20/21)
|
69.6%
(39/56)
|
91.3%
(84/92)
|
94.6%
(610/645)
|
package com.ghost.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class GhostPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = createExtension(project)
val ghostVersion = extension.version
// 1. Core & KSP (Reactive)
var kspSetupDone = false
fun configureKspDependencies() {
if (kspSetupDone) return
if (!project.plugins.hasPlugin(PLUGIN_KSP)) return
val compilerDep = ghostVersion.map { "$GROUP_ID:$ARTIFACT_COMPILER:$it" }
if (project.plugins.hasPlugin(PLUGIN_KMP)) {
val kotlinExtension = project
.extensions
.findByType(KotlinMultiplatformExtension::class.java)
kotlinExtension?.targets?.configureEach {
if (name == TARGET_METADATA) {
project.dependencies.add(CONFIG_KSP_COMMON, compilerDep)
} else {
val capitalizedTarget = name.replaceFirstChar { it.uppercase() }
project.dependencies.add(
"$PREFIX_KSP$capitalizedTarget",
compilerDep
)
}
}
kspSetupDone = true
} else if (project.plugins.hasPlugin(PLUGIN_ANDROID_APP) ||
project.plugins.hasPlugin(PLUGIN_ANDROID_LIB) ||
project.plugins.hasPlugin(PLUGIN_JVM)
) {
project.dependencies.add(
PREFIX_KSP,
compilerDep
)
kspSetupDone = true
}
}
project.plugins.withId(PLUGIN_KSP) { configureKspDependencies() }
project.plugins.withId(PLUGIN_KMP) {
setupKmp(project, ghostVersion)
configureKspDependencies()
}
var coreApplied = false
listOf(PLUGIN_ANDROID_APP, PLUGIN_ANDROID_LIB, PLUGIN_JVM).forEach { pluginId ->
project.plugins.withId(pluginId) {
if (!coreApplied) {
setupAndroidOrJvmCore(project, ghostVersion)
coreApplied = true
}
configureKspDependencies()
}
}
// 2. Network Adapters (Safe afterEvaluate)
// We use afterEvaluate to perform the check ONCE after all dependencies are declared,
// avoiding the circularity of providers and the variant resolution issues of configurations.all.
project.afterEvaluate {
val version = ghostVersion.get()
if (extension.autoInjectKtor.get() && hasKtorDependency(project)) {
injectNetworkDependency(project, "$GROUP_ID:$ARTIFACT_KTOR:$version")
}
if (extension.autoInjectRetrofit.get() && hasRetrofitDependency(project)) {
injectNetworkDependency(project, "$GROUP_ID:$ARTIFACT_RETROFIT:$version")
}
if (extension.autoInjectProtobuf.get() && hasProtobufDependency(project)) {
injectNetworkDependency(project, "$GROUP_ID:$ARTIFACT_PROTOBUF:$version")
}
}
}
private fun createExtension(project: Project): GhostExtension {
return project.extensions.create(EXTENSION_NAME, GhostExtension::class.java).apply {
autoInjectKtor.convention(true)
autoInjectRetrofit.convention(true)
autoInjectProtobuf.convention(true)
version.convention(DEFAULT_VERSION)
}
}
private fun setupKmp(project: Project, version: Provider<String>) {
val runtimeDep = version.map { "$GROUP_ID:$ARTIFACT_RUNTIME:$it" }
val apiDep = version.map { "$GROUP_ID:$ARTIFACT_API:$it" }
project.dependencies.add(CONFIG_COMMON_MAIN_IMPL, runtimeDep)
project.dependencies.add(CONFIG_COMMON_MAIN_IMPL, apiDep)
}
private fun setupAndroidOrJvmCore(project: Project, version: Provider<String>) {
val runtimeDep = version.map { "$GROUP_ID:$ARTIFACT_RUNTIME:$it" }
val apiDep = version.map { "$GROUP_ID:$ARTIFACT_API:$it" }
project.dependencies.add(CONFIG_IMPL, runtimeDep)
project.dependencies.add(CONFIG_IMPL, apiDep)
}
private fun isMultiplatform(project: Project): Boolean {
return project.pluginManager.hasPlugin(PLUGIN_KMP)
}
private fun hasKtorDependency(project: Project): Boolean {
return listOf(CONFIG_IMPL, "api", CONFIG_COMMON_MAIN_IMPL).any { name ->
val config = project.configurations.findByName(name)
config?.dependencies?.any {
it.group == GROUP_KTOR &&
it.name.startsWith(PREFIX_KTOR_CLIENT)
} ?: false
}
}
private fun hasRetrofitDependency(project: Project): Boolean {
return listOf(CONFIG_IMPL, "api", CONFIG_COMMON_MAIN_IMPL).any { name ->
val config = project.configurations.findByName(name)
config?.dependencies?.any {
it.group == GROUP_RETROFIT &&
it.name == NAME_RETROFIT
} ?: false
}
}
private fun hasProtobufDependency(project: Project): Boolean {
if (project.plugins.hasPlugin(PLUGIN_PROTOBUF)) return true
return listOf(CONFIG_IMPL, "api", CONFIG_COMMON_MAIN_IMPL).any { name ->
val config = project.configurations.findByName(name)
config?.dependencies?.any {
it.group == GROUP_PROTOBUF
} ?: false
}
}
private fun injectNetworkDependency(project: Project, dep: String) {
if (project.pluginManager.hasPlugin(PLUGIN_KMP)) {
project.dependencies.add(
CONFIG_COMMON_MAIN_IMPL,
dep
)
} else {
project.dependencies.add(
CONFIG_IMPL,
dep
)
}
}
companion object {
private const val EXTENSION_NAME = "ghost"
private const val DEFAULT_VERSION = "1.2.7"
private const val PLUGIN_KSP = "com.google.devtools.ksp"
private const val PLUGIN_KMP = "org.jetbrains.kotlin.multiplatform"
private const val PLUGIN_ANDROID_APP = "com.android.application"
private const val PLUGIN_ANDROID_LIB = "com.android.library"
private const val PLUGIN_JVM = "org.jetbrains.kotlin.jvm"
private const val PLUGIN_PROTOBUF = "com.google.protobuf"
private const val GROUP_ID = "com.ghostserializer"
private const val ARTIFACT_COMPILER = "ghost-compiler"
private const val ARTIFACT_RUNTIME = "ghost-serialization"
private const val ARTIFACT_API = "ghost-api"
private const val ARTIFACT_KTOR = "ghost-ktor"
private const val ARTIFACT_RETROFIT = "ghost-retrofit"
private const val ARTIFACT_PROTOBUF = "ghost-protobuf"
private const val CONFIG_COMMON_MAIN_IMPL = "commonMainImplementation"
private const val CONFIG_IMPL = "implementation"
private const val CONFIG_KSP_COMMON = "kspCommonMainMetadata"
private const val PREFIX_KSP = "ksp"
private const val TARGET_METADATA = "metadata"
private const val GROUP_KTOR = "io.ktor"
private const val PREFIX_KTOR_CLIENT = "ktor-client"
private const val GROUP_RETROFIT = "com.squareup.retrofit2"
private const val NAME_RETROFIT = "retrofit"
private const val GROUP_PROTOBUF = "com.google.protobuf"
}
}