Skip to content

Commit 10b02c5

Browse files
authored
Merge pull request #429 from gradle/ksp_workaround
Ksp Workaround
2 parents 8a39b2d + 88322bf commit 10b02c5

9 files changed

+440
-88
lines changed

src/main/groovy/org/gradle/android/workarounds/RoomSchemaLocationWorkaround.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.gradle.android.AndroidIssue
44
import org.gradle.android.VersionNumber
55
import org.gradle.android.workarounds.room.JavaCompileWorkaround
66
import org.gradle.android.workarounds.room.KaptWorkaround
7+
import org.gradle.android.workarounds.room.KspWorkaround
78
import org.gradle.android.workarounds.room.KotlinVersion
89
import org.gradle.android.workarounds.room.RoomExtension
910
import org.gradle.android.workarounds.room.task.RoomSchemaLocationMergeTask
@@ -68,5 +69,9 @@ class RoomSchemaLocationWorkaround implements Workaround {
6869
KaptWorkaround.create(project, roomExtension, mergeTask)
6970
javaCompileRoomTask.javaCompileSchemaGenerationEnabled = false
7071
}
72+
73+
project.plugins.withId("com.google.devtools.ksp") {
74+
KspWorkaround.create(project, roomExtension, mergeTask)
75+
}
7176
}
7277
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.gradle.android.workarounds.room
2+
3+
import org.gradle.android.workarounds.room.argumentprovider.KspRoomSchemaLocationArgumentProvider
4+
import org.gradle.android.workarounds.room.task.RoomSchemaLocationMergeTask
5+
import org.gradle.api.Project
6+
import org.gradle.api.Task
7+
import org.gradle.api.execution.TaskExecutionGraph
8+
import org.gradle.api.file.Directory
9+
import org.gradle.api.internal.file.FileOperations
10+
import org.gradle.api.provider.Provider
11+
import org.gradle.api.tasks.TaskProvider
12+
13+
class KspWorkaround extends AnnotationProcessorWorkaround<KspRoomSchemaLocationArgumentProvider> {
14+
private static final String KSP_TASK = "com.google.devtools.ksp.gradle.KspTaskJvm_Decorated"
15+
16+
KspWorkaround(Project project, RoomExtension extension, TaskProvider<RoomSchemaLocationMergeTask> mergeTask) {
17+
super(project, extension, mergeTask)
18+
}
19+
20+
static KspWorkaround create(Project project, RoomExtension extension, TaskProvider<RoomSchemaLocationMergeTask> mergeTask) {
21+
return new KspWorkaround(project, extension, mergeTask)
22+
}
23+
24+
@Override
25+
void initWorkaround() {
26+
project.tasks.matching({ it.class.name == KSP_TASK }).configureEach {
27+
if (roomExtension.schemaLocationDir.isPresent()) {
28+
configureWorkaroundTask(it)
29+
}
30+
}
31+
}
32+
33+
@Override
34+
void configureWorkaroundTask(Task task) {
35+
36+
def fileOperations = project.fileOperations
37+
def schemaLocationDir = roomExtension.schemaLocationDir
38+
39+
def variantSpecificSchemaDir = project.objects.directoryProperty()
40+
KspRoomSchemaLocationArgumentProvider provider = new KspRoomSchemaLocationArgumentProvider(roomExtension.schemaLocationDir, variantSpecificSchemaDir)
41+
variantSpecificSchemaDir.set(androidVariantProvider.getVariantSpecificSchemaDir(project, "${task.name}"))
42+
task.commandLineArgumentProviders.add(provider)
43+
44+
task.doFirst {
45+
KspWorkaround.copyExistingSchemasToTaskSpecificTmpDir(fileOperations, schemaLocationDir, provider)
46+
}
47+
48+
task.doLast {
49+
KspWorkaround.copyGeneratedSchemasToOutput(fileOperations, provider)
50+
}
51+
52+
task.finalizedBy {
53+
mergeTask
54+
}
55+
56+
TaskExecutionGraph taskGraph = project.gradle.taskGraph
57+
taskGraph.whenReady {
58+
if (taskGraph.hasTask(task)) {
59+
roomExtension.registerOutputDirectory(provider.schemaLocationDir)
60+
}
61+
}
62+
}
63+
64+
static void copyExistingSchemasToTaskSpecificTmpDir(FileOperations fileOperations, Provider<Directory> existingSchemaDir, KspRoomSchemaLocationArgumentProvider provider) {
65+
if (existingSchemaDir.isPresent()) {
66+
def temporaryVariantSpecificSchemaDir = provider.temporarySchemaLocationDir
67+
fileOperations.sync {
68+
it.from existingSchemaDir
69+
it.into temporaryVariantSpecificSchemaDir
70+
}
71+
}
72+
}
73+
74+
static void copyGeneratedSchemasToOutput(FileOperations fileOperations, KspRoomSchemaLocationArgumentProvider provider) {
75+
def variantSpecificSchemaDir = provider.schemaLocationDir
76+
def temporaryVariantSpecificSchemaDir = provider.temporarySchemaLocationDir
77+
fileOperations.sync {
78+
it.from temporaryVariantSpecificSchemaDir
79+
it.into variantSpecificSchemaDir
80+
}
81+
}
82+
83+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.gradle.android.workarounds.room.argumentprovider
2+
3+
import org.gradle.api.file.Directory
4+
import org.gradle.api.provider.Provider
5+
import org.gradle.api.tasks.Internal
6+
7+
class KspRoomSchemaLocationArgumentProvider extends RoomSchemaLocationArgumentProvider {
8+
@Internal Provider<Directory> temporarySchemaLocationDir
9+
10+
KspRoomSchemaLocationArgumentProvider(Provider<Directory> configuredSchemaLocationDir, Provider<Directory> schemaLocationDir) {
11+
super(configuredSchemaLocationDir, schemaLocationDir)
12+
this.temporarySchemaLocationDir = schemaLocationDir.map { it.dir("../${it.asFile.name}Temp") }
13+
}
14+
15+
@Override
16+
protected String getSchemaLocationPath() {
17+
return temporarySchemaLocationDir.get().asFile.absolutePath
18+
}
19+
}

src/main/groovy/org/gradle/android/workarounds/room/argumentprovider/RoomSchemaLocationArgumentProvider.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ abstract class RoomSchemaLocationArgumentProvider implements CommandLineArgument
3030
@Override
3131
Iterable<String> asArguments() {
3232
if (configuredSchemaLocationDir.isPresent()) {
33-
return ["-A${RoomSchemaLocationWorkaround.ROOM_SCHEMA_LOCATION}=${schemaLocationPath}" as String]
33+
if (this instanceof KspRoomSchemaLocationArgumentProvider) {
34+
return ["${RoomSchemaLocationWorkaround.ROOM_SCHEMA_LOCATION}=${schemaLocationPath}" as String]
35+
} else {
36+
return ["-A${RoomSchemaLocationWorkaround.ROOM_SCHEMA_LOCATION}=${schemaLocationPath}" as String]
37+
}
3438
} else {
3539
return []
3640
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package org.gradle.android
2+
3+
import org.gradle.testkit.runner.BuildResult
4+
import org.gradle.testkit.runner.TaskOutcome
5+
import org.junit.Assume
6+
import spock.lang.Unroll
7+
8+
import static org.gradle.testkit.runner.TaskOutcome.FROM_CACHE
9+
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
10+
11+
@MultiVersionTest
12+
class RoomSchemaLocationKspWorkaroundTest extends RoomWorkaroundAbstractTest {
13+
14+
@Unroll
15+
def "schemas are generated with Ksp into task-specific directory and are cacheable (Android #androidVersion) (Kotlin #kotlinVersion)"() {
16+
def kotlinVersionNumber = VersionNumber.parse(kotlinVersion)
17+
18+
// The Room workaround for KSP does not support Kotlin version 1.6.x or lower
19+
Assume.assumeTrue(kotlinVersionNumber >= VersionNumber.parse("1.7.0"))
20+
21+
SimpleAndroidApp.builder(temporaryFolder.root, cacheDir)
22+
.withAndroidVersion(androidVersion)
23+
.withKotlinVersion(kotlinVersionNumber)
24+
.withKspEnabled()
25+
.build()
26+
.writeProject()
27+
28+
cacheDir.deleteDir()
29+
cacheDir.mkdirs()
30+
31+
when:
32+
BuildResult buildResult = withGradleVersion(TestVersions.latestSupportedGradleVersionFor(androidVersion).version)
33+
.forwardOutput()
34+
.withProjectDir(temporaryFolder.root)
35+
.withArguments(CLEAN_BUILD)
36+
.build()
37+
38+
then:
39+
assertCompileTasksHaveOutcome(buildResult, SUCCESS)
40+
assertCompileAndroidTestTasksHaveOutcome(buildResult, SUCCESS)
41+
assertCompileUnitTestTasksHaveOutcome(buildResult, SUCCESS)
42+
assertKspTasksHaveOutcome(buildResult, SUCCESS)
43+
assertKspAndroidTestTasksHaveOutcome(buildResult, SUCCESS)
44+
assertKspUnitTestTasksHaveOutcome(buildResult, SUCCESS)
45+
buildResult.task(':app:mergeRoomSchemaLocations').outcome == SUCCESS
46+
buildResult.task(':library:mergeRoomSchemaLocations').outcome == SUCCESS
47+
48+
and:
49+
assertKspSchemaOutputsExist()
50+
51+
and:
52+
assertMergedSchemaOutputsExist()
53+
54+
when:
55+
buildResult = withGradleVersion(TestVersions.latestSupportedGradleVersionFor(androidVersion).version)
56+
.forwardOutput()
57+
.withProjectDir(temporaryFolder.root)
58+
.withArguments(CLEAN_BUILD)
59+
.build()
60+
61+
then:
62+
assertCompileTasksHaveOutcome(buildResult, FROM_CACHE)
63+
assertCompileAndroidTestTasksHaveOutcome(buildResult, FROM_CACHE)
64+
assertCompileUnitTestTasksHaveOutcome(buildResult, FROM_CACHE)
65+
assertKspTasksHaveOutcome(buildResult, FROM_CACHE)
66+
assertKspAndroidTestTasksHaveOutcome(buildResult, FROM_CACHE)
67+
assertKspUnitTestTasksHaveOutcome(buildResult, FROM_CACHE)
68+
buildResult.task(':app:mergeRoomSchemaLocations').outcome == SUCCESS
69+
buildResult.task(':library:mergeRoomSchemaLocations').outcome == SUCCESS
70+
71+
and:
72+
assertKspSchemaOutputsExist()
73+
74+
and:
75+
assertMergedSchemaOutputsExist()
76+
77+
and:
78+
assertKspSchemaContainsColumnFor('last_update', 'debug')
79+
80+
where:
81+
//noinspection GroovyAssignabilityCheck
82+
[androidVersion, kotlinVersion] << [TestVersions.latestAndroidVersions, TestVersions.supportedKotlinVersions.keySet()].combinations()
83+
}
84+
85+
@Unroll
86+
def "schemas are correctly generated with Ksp when only one variant is built incrementally (Android #androidVersion) (Kotlin #kotlinVersion)"() {
87+
def kotlinVersionNumber = VersionNumber.parse(kotlinVersion)
88+
89+
// The Room workaround for KSP does not support Kotlin version 1.6.x or lower
90+
Assume.assumeTrue(kotlinVersionNumber >= VersionNumber.parse("1.7.0"))
91+
92+
SimpleAndroidApp.builder(temporaryFolder.root, cacheDir)
93+
.withAndroidVersion(androidVersion)
94+
.withKotlinVersion(kotlinVersionNumber)
95+
.withKspEnabled()
96+
.build()
97+
.writeProject()
98+
99+
cacheDir.deleteDir()
100+
cacheDir.mkdirs()
101+
102+
when:
103+
BuildResult buildResult = withGradleVersion(TestVersions.latestSupportedGradleVersionFor(androidVersion).version)
104+
.forwardOutput()
105+
.withProjectDir(temporaryFolder.root)
106+
.withArguments(CLEAN_BUILD)
107+
.build()
108+
109+
then:
110+
assertCompileTasksHaveOutcome(buildResult, SUCCESS)
111+
assertCompileAndroidTestTasksHaveOutcome(buildResult, SUCCESS)
112+
assertCompileUnitTestTasksHaveOutcome(buildResult, SUCCESS)
113+
assertKspTasksHaveOutcome(buildResult, SUCCESS)
114+
assertKspAndroidTestTasksHaveOutcome(buildResult, SUCCESS)
115+
assertKspUnitTestTasksHaveOutcome(buildResult, SUCCESS)
116+
117+
buildResult.task(':app:mergeRoomSchemaLocations').outcome == SUCCESS
118+
buildResult.task(':library:mergeRoomSchemaLocations').outcome == SUCCESS
119+
120+
and:
121+
assertKspSchemaOutputsExist()
122+
123+
and:
124+
assertMergedSchemaOutputsExist()
125+
126+
and:
127+
assertKspSchemaContainsColumnFor('last_update', 'debug')
128+
129+
and:
130+
assertMergedRoomSchemaContainsColumn("last_update")
131+
132+
when:
133+
modifyRoomColumnName("last_update", "foo")
134+
buildResult = withGradleVersion(TestVersions.latestSupportedGradleVersionFor(androidVersion).version)
135+
.forwardOutput()
136+
.withProjectDir(temporaryFolder.root)
137+
.withArguments(INCREMENTAL_DEBUG_BUILD)
138+
.build()
139+
140+
then:
141+
assertCompileTasksHaveOutcome(buildResult, SUCCESS, ["debug"])
142+
assertKspTasksHaveOutcome(buildResult, SUCCESS, ["debug"])
143+
buildResult.task(':app:mergeRoomSchemaLocations').outcome == SUCCESS
144+
buildResult.task(':library:mergeRoomSchemaLocations').outcome == SUCCESS
145+
146+
and:
147+
assertKspSchemaOutputsExist()
148+
149+
and:
150+
assertMergedSchemaOutputsExist()
151+
152+
and:
153+
assertKspSchemaContainsColumnFor('foo', 'debug')
154+
155+
and:
156+
assertMergedRoomSchemaContainsColumn("foo")
157+
158+
where:
159+
//noinspection GroovyAssignabilityCheck
160+
[androidVersion, kotlinVersion] << [TestVersions.latestAndroidVersions, TestVersions.supportedKotlinVersions.keySet()].combinations()
161+
162+
}
163+
164+
void assertKspAndroidTestTasksHaveOutcome(BuildResult buildResult, TaskOutcome outcome) {
165+
assertAllVariantTasksHaveOutcome(buildResult, outcome, ALL_PROJECTS, ["debug"]) { project, variant -> ":${project}:ksp${variant.capitalize()}AndroidTestKotlin" }
166+
}
167+
168+
void assertKspTasksHaveOutcome(BuildResult buildResult, TaskOutcome outcome, List<String> variants = ALL_VARIANTS) {
169+
assertAllVariantTasksHaveOutcome(buildResult, outcome, ALL_PROJECTS, variants) { project, variant -> ":${project}:ksp${variant.capitalize()}Kotlin" }
170+
}
171+
172+
void assertKspUnitTestTasksHaveOutcome(BuildResult buildResult, TaskOutcome outcome, List<String> variants = ALL_VARIANTS) {
173+
assertAllVariantTasksHaveOutcome(buildResult, outcome, ALL_PROJECTS, variants) { project, variant -> ":${project}:ksp${variant.capitalize()}UnitTestKotlin" }
174+
}
175+
176+
void assertKspSchemaOutputsExist() {
177+
assertKspSchemaOutputsExistFor("debug")
178+
assertKspSchemaOutputsExistFor("release")
179+
}
180+
181+
void assertKspSchemaOutputsExistFor(String variant) {
182+
assertSchemasExist("app", "build/roomSchemas/ksp${variant.capitalize()}Kotlin")
183+
assertSchemasExist("library", "build/roomSchemas/ksp${variant.capitalize()}Kotlin")
184+
}
185+
186+
void assertKspSchemaContainsColumnFor(String columnName, String variant) {
187+
assertRoomSchemaContainsColumn("app", "build/roomSchemas/ksp${variant.capitalize()}Kotlin", columnName)
188+
assertRoomSchemaContainsColumn("library", "build/roomSchemas/ksp${variant.capitalize()}Kotlin", columnName)
189+
}
190+
}

0 commit comments

Comments
 (0)