Skip to content

Commit dc08add

Browse files
committed
Update test to enable withJava() for android target
1 parent 3b04a9f commit dc08add

File tree

16 files changed

+546
-13
lines changed

16 files changed

+546
-13
lines changed

gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspConfigurations.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ class KspConfigurations(private val project: Project) {
188188
}
189189

190190
private fun isMppProject() = project.pluginManager.hasPlugin("kotlin-multiplatform")
191-
192191
/**
193192
* Returns the user-facing configurations involved in the given compilation.
194193
* We use [KotlinCompilation.kotlinSourceSets], not [KotlinCompilation.allKotlinSourceSets] for a few reasons:

gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.google.devtools.ksp.gradle.utils.canUseInternalKspApis
2525
import com.google.devtools.ksp.gradle.utils.checkMinimumAgpVersion
2626
import com.google.devtools.ksp.gradle.utils.enableProjectIsolationCompatibleCodepath
2727
import com.google.devtools.ksp.gradle.utils.isAgpBuiltInKotlinUsed
28+
import com.google.devtools.ksp.gradle.utils.minimumAndroidKotlinMultiplatformVersion
2829
import com.google.devtools.ksp.gradle.utils.useLegacyVariantApi
2930
import org.gradle.api.GradleException
3031
import org.gradle.api.Project
@@ -261,17 +262,19 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool
261262
)
262263
}
263264

265+
// The variant API in KMP runs after KotlinCompilerPluginSupportPlugin.applyToCompilation()
266+
// afterEvaluate is needed here to ensure the Variant API onVariants callback has run and populated the
267+
// androidComponentCache, before we attempt to query for the Component object
264268
project.afterEvaluate {
265-
try {
266-
if (kotlinCompilation is KotlinMultiplatformAndroidCompilation) {
267-
val component = androidComponentCache.get(kotlinCompilation.componentName)
268-
AndroidPluginIntegration.registerGeneratedSourcesKmp(
269-
kspTaskProvider = kspTaskProvider,
270-
androidComponent = component,
271-
)
272-
}
273-
} catch (e: NoClassDefFoundError) {
274-
// ignore
269+
if (project.isAndroidKmpProject() &&
270+
project.minimumAndroidKotlinMultiplatformVersion() &&
271+
kotlinCompilation is KotlinMultiplatformAndroidCompilation
272+
) {
273+
val component = androidComponentCache.get(kotlinCompilation.componentName)
274+
AndroidPluginIntegration.registerGeneratedSourcesKmp(
275+
kspTaskProvider = kspTaskProvider,
276+
androidComponent = component,
277+
)
275278
}
276279
}
277280

@@ -493,3 +496,8 @@ internal fun FileCollection.nonSelfDeps(selfTaskName: String): List<Task> =
493496

494497
internal fun getKaptGeneratedClassesDir(project: Project, sourceSetName: String) =
495498
Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, sourceSetName)
499+
500+
private fun Project.isAndroidKmpProject(): Boolean {
501+
return project.pluginManager.hasPlugin("kotlin-multiplatform") &&
502+
project.pluginManager.hasPlugin("com.android.kotlin.multiplatform.library")
503+
}

gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/utils/agpUtils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ fun Project.canUseInternalKspApis(): Boolean {
5656
return agpVersion >= AndroidPluginVersion(9, 0, 0).alpha(14)
5757
}
5858

59+
fun Project.minimumAndroidKotlinMultiplatformVersion(): Boolean {
60+
val agpVersion = project.getAgpVersion() ?: return false
61+
return agpVersion >= AndroidPluginVersion(8, 2, 0)
62+
}
63+
5964
/**
6065
* Defines the minimum supported Android Gradle Plugin (AGP) version.
6166
*

integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KMPImplementedIT.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class KMPImplementedIT {
5151
"clean",
5252
":workload-android:build"
5353
).build().let {
54-
// Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-android:build")?.outcome)
54+
Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-android:build")?.outcome)
5555
verify(
5656
"workload-android/build/intermediates/compile_library_classes_jar/androidMain/" +
5757
"bundleAndroidMainClassesToCompileJar/classes.jar",

integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MultiplatformIT.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import java.util.jar.*
1414
class MultiplatformIT {
1515
@Rule
1616
@JvmField
17-
val project: TemporaryTestProject = TemporaryTestProject("playground-mpp", "playground")
17+
val project: TemporaryTestProject = TemporaryTestProject("playground-mpp")
1818

1919
@Test
2020
fun testJVM() {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.example.annotation
2+
3+
annotation class Builder
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import com.google.devtools.ksp.getConstructors
2+
import com.google.devtools.ksp.containingFile
3+
import com.google.devtools.ksp.processing.*
4+
import com.google.devtools.ksp.symbol.*
5+
import com.google.devtools.ksp.validate
6+
import java.io.OutputStream
7+
8+
fun OutputStream.appendText(str: String) {
9+
this.write(str.toByteArray())
10+
}
11+
12+
class BuilderProcessor : SymbolProcessor {
13+
lateinit var codeGenerator: CodeGenerator
14+
lateinit var logger: KSPLogger
15+
16+
fun init(
17+
options: Map<String, String>,
18+
kotlinVersion: KotlinVersion,
19+
codeGenerator: CodeGenerator,
20+
logger: KSPLogger,
21+
) {
22+
this.codeGenerator = codeGenerator
23+
this.logger = logger
24+
}
25+
26+
override fun process(resolver: Resolver): List<KSAnnotated> {
27+
val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Builder")
28+
val ret = symbols.filter { !it.validate() }
29+
symbols
30+
.filter { it is KSClassDeclaration && it.validate() }
31+
.forEach { it.accept(BuilderVisitor(), Unit) }
32+
return ret.toList()
33+
}
34+
35+
inner class BuilderVisitor : KSVisitorVoid() {
36+
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
37+
val constructor = classDeclaration.primaryConstructor
38+
?: classDeclaration.getConstructors().maxByOrNull { it.parameters.size }
39+
40+
if (constructor == null) {
41+
logger.error("No suitable constructor found for ${classDeclaration.qualifiedName?.asString()}", classDeclaration)
42+
return
43+
}
44+
45+
constructor.accept(this, data)
46+
}
47+
48+
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
49+
val parent = function.parentDeclaration as KSClassDeclaration
50+
val packageName = parent.containingFile!!.packageName.asString()
51+
val className = "${parent.simpleName.asString()}Builder"
52+
53+
// For regression testing https://github.com/google/ksp/pull/467
54+
codeGenerator.createNewFile(
55+
Dependencies(true, function.containingFile!!),
56+
"",
57+
"META-INF/proguard/builder-$className",
58+
"pro"
59+
).use { proguardFile ->
60+
proguardFile.appendText("-keep class $packageName.$className { *; }")
61+
}
62+
63+
val file = codeGenerator.createNewFile(
64+
Dependencies(true, function.containingFile!!), packageName, className
65+
)
66+
file.appendText("package $packageName\n\n")
67+
file.appendText("import hello.HELLO\n\n")
68+
file.appendText("class $className{\n")
69+
function.parameters.forEach {
70+
val name = it.name!!.asString()
71+
val typeName = StringBuilder(it.type.resolve().declaration.qualifiedName?.asString() ?: "<ERROR>")
72+
val typeArgs = it.type.element!!.typeArguments
73+
if (it.type.element!!.typeArguments.toList().isNotEmpty()) {
74+
typeName.append("<")
75+
typeName.append(
76+
typeArgs.map {
77+
val type = it.type?.resolve()
78+
"${it.variance.label} ${type?.declaration?.qualifiedName?.asString() ?: "ERROR"}" +
79+
if (type?.nullability == Nullability.NULLABLE) "?" else ""
80+
}.joinToString(", ")
81+
)
82+
typeName.append(">")
83+
}
84+
file.appendText(" private var $name: $typeName? = null\n")
85+
file.appendText(" internal fun with${name.capitalize()}($name: $typeName): $className {\n")
86+
file.appendText(" this.$name = $name\n")
87+
file.appendText(" return this\n")
88+
file.appendText(" }\n\n")
89+
}
90+
file.appendText(" internal fun build(): ${parent.qualifiedName!!.asString()} {\n")
91+
file.appendText(" return ${parent.qualifiedName!!.asString()}(")
92+
file.appendText(
93+
function.parameters.map {
94+
"${it.name!!.asString()}!!"
95+
}.joinToString(", ")
96+
)
97+
file.appendText(")\n")
98+
file.appendText(" }\n")
99+
file.appendText("}\n")
100+
file.close()
101+
}
102+
}
103+
}
104+
105+
class TestProcessorProvider : SymbolProcessorProvider {
106+
override fun create(
107+
env: SymbolProcessorEnvironment,
108+
): SymbolProcessor {
109+
return BuilderProcessor().apply {
110+
init(env.options, env.kotlinVersion, env.codeGenerator, env.logger)
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)