Skip to content

Commit 253cfdd

Browse files
author
Adrián García
authored
Add experimental support for incremental annotation processing in mini-processor (#13)
* Add experimental support for incremental annotation processing in mini-processor * Update mini-processor/build.gradle * Apply suggestions from code review
1 parent 9816c7d commit 253cfdd

File tree

9 files changed

+126
-52
lines changed

9 files changed

+126
-52
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- Add support for incremental annotation processing.
810
### Fixed
911
- Fix sources not getting attached to some packages, now they should be visible from Android Studio.
1012

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,31 @@ dependencies {
202202
}
203203
```
204204

205+
### Recommended settings
206+
#### JDK8 requirements
207+
Ensure that your project has compatibility with Java 8:
208+
```groovy
209+
android {
210+
compileOptions {
211+
sourceCompatibility = JavaVersion.VERSION_1_8
212+
targetCompatibility = JavaVersion.VERSION_1_8
213+
}
214+
215+
kotlinOptions {
216+
jvmTarget = "1.8"
217+
}
218+
}
219+
```
220+
#### Improve compilation speed
221+
In order to speed up the compilation process, it is recommended to add the following settings in
222+
your `gradle.properties`:
223+
```groovy
224+
## Improves kapt speed with parallel annotation processing tasks, may impact in memory usage
225+
kapt.use.worker.api=true
226+
## Enables Gradle build cache
227+
org.gradle.caching=true
228+
```
229+
205230
### \[Android] Setting up your App file
206231

207232
You'll need to add the following snippet to your `Application`'s `onCreate` method. If you don't have it, then create it and reference it in your `AndroidManifest.xml` file:

app/build.gradle

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ android {
4444
}
4545

4646
compileOptions {
47-
sourceCompatibility "1.7"
48-
targetCompatibility "1.7"
47+
sourceCompatibility "1.8"
48+
targetCompatibility "1.8"
49+
}
50+
51+
kotlinOptions {
52+
jvmTarget = "1.8"
4953
}
5054

5155
lintOptions {

app/src/main/java/org/sample/SampleData.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@ interface ActionInterface {
99
val text: String
1010
}
1111

12+
@Action
13+
class ActionOne(override val text: String) : ActionInterface
14+
1215
@Action
1316
class ActionTwo(override val text: String) : ActionInterface
1417

1518
data class DummyState(val text: String = "dummy")
1619
class DummyStore : Store<DummyState>() {
20+
@Reducer
21+
fun onActionOne(action: ActionOne) {
22+
newState = state.copy(text = "${state.text} ${action.text}")
23+
}
24+
1725
@Reducer
1826
fun onActionTwo(action: ActionTwo) {
1927
newState = state.copy(text = action.text)

gradle.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ android.enableJetifier=true
1313
# This option should only be used with decoupled projects. More details, visit
1414
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
1515
# org.gradle.parallel=true
16+
kapt.use.worker.api=true
17+
kapt.include.compile.classpath=false
18+
19+
org.gradle.caching=true
20+
org.gradle.parallel=true

mini-processor/build.gradle

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
apply plugin: 'kotlin'
2+
apply plugin: 'kotlin-kapt'
23
apply from: "../jitpack.gradle"
34

45
dependencies {
56
api project(":mini-common")
67
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
7-
implementation 'com.github.yanex:takenoko:0.1'
8-
implementation 'com.squareup:kotlinpoet:1.3.0'
8+
implementation 'com.squareup:kotlinpoet:1.5.0'
9+
10+
// Lib to add incremental annotation processing
11+
def incap_version = "0.2"
12+
compileOnly "net.ltgt.gradle.incap:incap:$incap_version"
13+
kapt "net.ltgt.gradle.incap:incap-processor:$incap_version"
914

1015
testImplementation 'junit:junit:4.12'
1116
testImplementation 'com.google.testing.compile:compile-testing:0.15'
12-
}
17+
}

mini-processor/src/main/java/mini/processor/ActionTypesGenerator.kt

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,36 @@ object ActionTypesGenerator {
2222

2323
val prop = PropertySpec.builder("actionTypes", mapType)
2424
.addModifiers(KModifier.PRIVATE)
25-
//⇤⇥«»
2625
.initializer(CodeBlock.builder()
27-
.add("mapOf(\n")
26+
.addStatement("mapOf(")
27+
.indent()
2828
.apply {
2929
actionModels.forEach { actionModel ->
3030
val comma = if (actionModel != actionModels.last()) "," else ""
31-
add("«")
31+
add("«") // Starts statement
3232
add("%T::class to ", actionModel.typeName)
3333
add(actionModel.listOfSupertypesCodeBlock())
3434
add(comma)
35-
add("\n»")
35+
add("\n")
36+
add("»") // Ends statement
3637
}
3738
}
38-
.add("⇤)")
39+
.unindent()
40+
.add(")")
3941
.build())
4042
addProperty(prop.build())
4143
}.build()
4244
}
4345
}
4446

45-
class ActionModel(val element: Element) {
46-
val type = element.asType()
47-
val typeName = type.asTypeName()
48-
val superTypes = collectTypes(type)
47+
private class ActionModel(element: Element) {
48+
private val type = element.asType()
49+
private val superTypes = collectTypes(type)
4950
.sortedBy { it.depth }
50-
//Ignore base types
51-
.filter { it.mirror.qualifiedName() != "java.lang.Object" }
52-
.filter { it.mirror.qualifiedName() != "mini.BaseAction" }
51+
// Ignore base types
52+
.filter { it.mirror.qualifiedName() !in listOf("java.lang.Object", "mini.BaseAction") }
53+
val typeName = type.asTypeName()
54+
5355

5456
fun listOfSupertypesCodeBlock(): CodeBlock {
5557
val format = superTypes.joinToString(",\n") { "%T::class" }
@@ -58,16 +60,15 @@ class ActionModel(val element: Element) {
5860
}
5961

6062
private fun collectTypes(mirror: TypeMirror, depth: Int = 0): Set<ActionSuperType> {
61-
//We want to add by depth
63+
// We want to add by depth
6264
val superTypes = typeUtils.directSupertypes(mirror).toSet()
6365
.map { collectTypes(it, depth + 1) }
6466
.flatten()
6567
return setOf(ActionSuperType(mirror, depth)) + superTypes
6668
}
6769

6870
class ActionSuperType(val mirror: TypeMirror, val depth: Int) {
69-
val element = mirror.asElement()
70-
val qualifiedName = element.qualifiedName()
71+
val qualifiedName = mirror.asElement().qualifiedName()
7172

7273
override fun equals(other: Any?): Boolean {
7374
if (this === other) return true
@@ -77,8 +78,6 @@ class ActionModel(val element: Element) {
7778
return true
7879
}
7980

80-
override fun hashCode(): Int {
81-
return qualifiedName.hashCode()
82-
}
81+
override fun hashCode(): Int = qualifiedName.hashCode()
8382
}
8483
}

mini-processor/src/main/java/mini/processor/MiniProcessor.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,37 @@ import com.squareup.kotlinpoet.FileSpec
44
import com.squareup.kotlinpoet.TypeSpec
55
import mini.Action
66
import mini.Reducer
7+
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
8+
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType
79
import javax.annotation.processing.*
810
import javax.lang.model.SourceVersion
911
import javax.lang.model.element.TypeElement
1012

13+
/**
14+
* Mini annotation processor.
15+
*
16+
* In order to support incremental annotation processing we must specify the kind of processing this
17+
* does. For our use case, the processor looks up all the classes in the project in order to find
18+
* [Reducer] and [Action] annotations and created a single [MiniGen] file with the info, so it would
19+
* make our processor an AGGREGATING one.
20+
*
21+
* More info and implementations can be found in:
22+
* Official docs:
23+
* - https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing
24+
*
25+
* Isolating incremental annotation processors implementations:
26+
* - https://github.com/square/moshi/pull/824
27+
* - https://github.com/JakeWharton/butterknife/pull/1546
28+
*
29+
* Aggregating incremental annotation processors implementations:
30+
* - https://github.com/greenrobot/EventBus/pull/617
31+
*
32+
* Libraries used:
33+
* - https://github.com/tbroyer/gradle-incap-helper
34+
*/
1135
@SupportedSourceVersion(SourceVersion.RELEASE_8)
1236
@SupportedOptions("kapt.kotlin.generated")
37+
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.AGGREGATING)
1338
class MiniProcessor : AbstractProcessor() {
1439

1540
override fun init(environment: ProcessingEnvironment) {
@@ -46,6 +71,4 @@ class MiniProcessor : AbstractProcessor() {
4671

4772
return true
4873
}
49-
50-
5174
}

mini-processor/src/main/java/mini/processor/ReducersGenerator.kt

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,56 @@ import javax.lang.model.element.Element
1010
import javax.lang.model.element.ExecutableElement
1111

1212
object ReducersGenerator {
13-
1413
fun generate(container: TypeSpec.Builder, elements: Set<Element>) {
1514
val reducers = elements.map { ReducerModel(it) }
1615
.groupBy { it.containerName }
1716

1817
val reducerContainerType = Any::class.asTypeName()
1918
val reducerContainerListType = List::class.asTypeName().parameterizedBy(reducerContainerType)
2019

21-
val whenBlock = CodeBlock.builder()
20+
val newDispatcherFn = FunSpec.builder("newDispatcher")
21+
.returns(Dispatcher::class)
22+
.addCode(CodeBlock.builder()
23+
.addStatement("return Dispatcher(actionTypes)")
24+
.build())
25+
.build()
26+
27+
val subscribeSingleWhenBlock = CodeBlock.builder()
2228
.addStatement("val c = %T()", CompositeCloseable::class)
23-
.addStatement("when (container) {").indent()
29+
.beginControlFlow("when (container)")
2430
.apply {
2531
reducers.forEach { (containerName, reducerFunctions) ->
26-
addStatement("is %T -> {", containerName).indent()
32+
beginControlFlow("is %T ->", containerName)
2733
reducerFunctions.forEach { function ->
2834
addStatement("c.add(dispatcher.subscribe<%T>(priority=%L) { container.%N(it) })",
2935
function.function.parameters[0].asType(), //Action type
3036
function.priority, //Priority
3137
function.function.simpleName //Function name
3238
)
3339
}
34-
unindent().addStatement("}")
40+
endControlFlow()
3541
}
3642
}
3743
.addStatement("else -> throw IllegalArgumentException(\"Container \$container has no reducers\")")
38-
.unindent()
39-
.addStatement("}") //Close when
44+
.endControlFlow()
4045
.addStatement("return c")
4146
.build()
4247

43-
val registerOneFn = FunSpec.builder("subscribe")
48+
val subscribeSingleFn = FunSpec.builder("subscribe")
4449
.addModifiers(KModifier.PRIVATE)
45-
.addParameter("dispatcher", Dispatcher::class)
46-
.addParameter("container", reducerContainerType)
50+
.addParameters(listOf(
51+
ParameterSpec.builder("dispatcher", Dispatcher::class).build(),
52+
ParameterSpec.builder("container", reducerContainerType).build()
53+
))
4754
.returns(Closeable::class)
48-
.addCode(whenBlock)
55+
.addCode(subscribeSingleWhenBlock)
4956
.build()
5057

51-
val registerListFn = FunSpec.builder("subscribe")
52-
.addParameter("dispatcher", Dispatcher::class)
53-
.addParameter("containers", reducerContainerListType)
58+
val subscribeListFn = FunSpec.builder("subscribe")
59+
.addParameters(listOf(
60+
ParameterSpec.builder("dispatcher", Dispatcher::class).build(),
61+
ParameterSpec.builder("containers", reducerContainerListType).build()
62+
))
5463
.returns(Closeable::class)
5564
.addStatement("val c = %T()", CompositeCloseable::class)
5665
.beginControlFlow("containers.forEach { container ->")
@@ -59,23 +68,17 @@ object ReducersGenerator {
5968
.addStatement("return c")
6069
.build()
6170

62-
val initDispatcherFn = FunSpec.builder("newDispatcher")
63-
.returns(Dispatcher::class)
64-
.addCode(CodeBlock.builder()
65-
.addStatement("return Dispatcher(actionTypes)")
66-
.build())
67-
.build()
68-
69-
container.addFunction(initDispatcherFn)
70-
container.addFunction(registerOneFn)
71-
container.addFunction(registerListFn)
72-
71+
with(container) {
72+
addFunction(newDispatcherFn)
73+
addFunction(subscribeSingleFn)
74+
addFunction(subscribeListFn)
75+
}
7376
}
7477
}
7578

76-
class ReducerModel(val element: Element) {
79+
private class ReducerModel(element: Element) {
7780
val priority = element.getAnnotation(Reducer::class.java).priority
7881
val function = element as ExecutableElement
79-
val container = element.enclosingElement.asType()
82+
val container = element.enclosingElement.asType()!!
8083
val containerName = container.asTypeName()
8184
}

0 commit comments

Comments
 (0)