Skip to content

Commit 53672c0

Browse files
authored
Merge pull request #836 from square/rick/incremental-fixes
incremental compilation fixes
2 parents d68776a + d7ca03d commit 53672c0

File tree

70 files changed

+3157
-340
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+3157
-340
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
### Custom Code Generator
1919

20+
- The `GeneratedFile` result type has been deprecated in favor of `GeneratedFileWithSources`. This new type allows for precise tracking of the generated files, which in turn drastically improves incremental compilation performance (#693).
21+
2022
### Other Notes & Contributions
2123

2224

build-logic/conventions/src/main/kotlin/com/squareup/anvil/conventions/BasePlugin.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.squareup.anvil.conventions
22

3+
import com.rickbusarow.kgx.buildDir
34
import com.rickbusarow.kgx.extras
45
import com.rickbusarow.kgx.fromInt
56
import com.rickbusarow.kgx.javaExtension
@@ -125,6 +126,7 @@ abstract class BasePlugin : Plugin<Project> {
125126
* so that there is no shared mutable state.
126127
*/
127128
private fun configureBuildDirs(target: Project) {
129+
128130
when {
129131
!target.isInAnvilBuild() -> return
130132

@@ -136,6 +138,16 @@ abstract class BasePlugin : Plugin<Project> {
136138
target.layout.buildDirectory.set(target.file("build/included-build"))
137139
}
138140
}
141+
142+
// Set the kase working directory ('<build directory>/kase/<test|gradleTest>') as a System property,
143+
// so that it's in the right place for projects with relocated directories.
144+
// https://github.com/rickbusarow/kase/blob/255db67f40d5ec83e31755bc9ce81b1a2b08cf11/kase/src/main/kotlin/com/rickbusarow/kase/files/HasWorkingDir.kt#L93-L96
145+
target.tasks.withType(Test::class.java).configureEach { task ->
146+
task.systemProperty(
147+
"kase.baseWorkingDir",
148+
target.buildDir().resolve("kase/${task.name}"),
149+
)
150+
}
139151
}
140152

141153
/**

compiler-api/api/compiler-api.api

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public abstract interface class com/squareup/anvil/compiler/api/AnvilContext {
4343
public abstract fun getGenerateFactories ()Z
4444
public abstract fun getGenerateFactoriesOnly ()Z
4545
public abstract fun getModule ()Lorg/jetbrains/kotlin/descriptors/ModuleDescriptor;
46+
public abstract fun getTrackSourceFiles ()Z
4647
}
4748

4849
public abstract interface class com/squareup/anvil/compiler/api/CodeGenerator : com/squareup/anvil/compiler/api/AnvilApplicabilityChecker {
@@ -52,17 +53,50 @@ public abstract interface class com/squareup/anvil/compiler/api/CodeGenerator :
5253

5354
public final class com/squareup/anvil/compiler/api/CodeGeneratorKt {
5455
public static final fun createGeneratedFile (Lcom/squareup/anvil/compiler/api/CodeGenerator;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/anvil/compiler/api/GeneratedFile;
56+
public static final fun createGeneratedFile (Lcom/squareup/anvil/compiler/api/CodeGenerator;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;[Ljava/io/File;)Lcom/squareup/anvil/compiler/api/GeneratedFileWithSources;
57+
public static final fun createGeneratedFile (Lcom/squareup/anvil/compiler/api/CodeGenerator;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)Lcom/squareup/anvil/compiler/api/GeneratedFileWithSources;
5558
}
5659

57-
public final class com/squareup/anvil/compiler/api/GeneratedFile {
60+
public abstract interface class com/squareup/anvil/compiler/api/FileWithContent : java/lang/Comparable {
61+
public abstract fun compareTo (Lcom/squareup/anvil/compiler/api/FileWithContent;)I
62+
public abstract fun component1 ()Ljava/io/File;
63+
public abstract fun component2 ()Ljava/lang/String;
64+
public abstract fun getContent ()Ljava/lang/String;
65+
public abstract fun getFile ()Ljava/io/File;
66+
}
67+
68+
public final class com/squareup/anvil/compiler/api/FileWithContent$DefaultImpls {
69+
public static fun compareTo (Lcom/squareup/anvil/compiler/api/FileWithContent;Lcom/squareup/anvil/compiler/api/FileWithContent;)I
70+
public static fun component1 (Lcom/squareup/anvil/compiler/api/FileWithContent;)Ljava/io/File;
71+
public static fun component2 (Lcom/squareup/anvil/compiler/api/FileWithContent;)Ljava/lang/String;
72+
}
73+
74+
public final class com/squareup/anvil/compiler/api/GeneratedFile : com/squareup/anvil/compiler/api/FileWithContent {
5875
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
76+
public fun compareTo (Lcom/squareup/anvil/compiler/api/FileWithContent;)I
77+
public synthetic fun compareTo (Ljava/lang/Object;)I
5978
public final fun component1 ()Ljava/io/File;
6079
public final fun component2 ()Ljava/lang/String;
6180
public final fun copy (Ljava/io/File;Ljava/lang/String;)Lcom/squareup/anvil/compiler/api/GeneratedFile;
6281
public static synthetic fun copy$default (Lcom/squareup/anvil/compiler/api/GeneratedFile;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/anvil/compiler/api/GeneratedFile;
6382
public fun equals (Ljava/lang/Object;)Z
64-
public final fun getContent ()Ljava/lang/String;
65-
public final fun getFile ()Ljava/io/File;
83+
public fun getContent ()Ljava/lang/String;
84+
public fun getFile ()Ljava/io/File;
85+
public fun hashCode ()I
86+
public fun toString ()Ljava/lang/String;
87+
}
88+
89+
public final class com/squareup/anvil/compiler/api/GeneratedFileWithSources : com/squareup/anvil/compiler/api/FileWithContent {
90+
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/util/Set;)V
91+
public fun compareTo (Lcom/squareup/anvil/compiler/api/FileWithContent;)I
92+
public synthetic fun compareTo (Ljava/lang/Object;)I
93+
public fun component1 ()Ljava/io/File;
94+
public fun component2 ()Ljava/lang/String;
95+
public final fun component3 ()Ljava/util/Set;
96+
public fun equals (Ljava/lang/Object;)Z
97+
public fun getContent ()Ljava/lang/String;
98+
public fun getFile ()Ljava/io/File;
99+
public final fun getSourceFiles ()Ljava/util/Set;
66100
public fun hashCode ()I
67101
public fun toString ()Ljava/lang/String;
68102
}

compiler-api/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,9 @@ dependencies {
2020
api(libs.kotlin.compiler)
2121

2222
implementation(platform(libs.kotlin.bom))
23+
24+
testImplementation(libs.junit)
25+
testImplementation(libs.kase)
26+
testImplementation(libs.kotest.assertions.api)
27+
testImplementation(libs.kotest.assertions.core.jvm)
2328
}

compiler-api/src/main/java/com/squareup/anvil/compiler/api/AnvilContext.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ public interface AnvilContext {
4343
*/
4444
public val disableComponentMerging: Boolean
4545

46+
/**
47+
* Enables incremental compilation support.
48+
*
49+
* This is achieved by tracking the source files for each generated file,
50+
* which allows for two new behaviors:
51+
* - Generated code is "invalidated" and deleted when the source file is changed or deleted.
52+
* - Generated code is cached in a way that Gradle understands,
53+
* and will be restored from cache along with other build artifacts.
54+
*/
55+
public val trackSourceFiles: Boolean
56+
4657
/**
4758
* The module of the current compilation.
4859
*/

compiler-api/src/main/java/com/squareup/anvil/compiler/api/CodeGenerator.kt

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,81 @@ public interface CodeGenerator : AnvilApplicabilityChecker {
3838
codeGenDir: File,
3939
module: ModuleDescriptor,
4040
projectFiles: Collection<KtFile>,
41-
): Collection<GeneratedFile>
41+
): Collection<FileWithContent>
4242
}
4343

4444
/**
4545
* Write [content] into a new file for the given [packageName] and [fileName]. [fileName] usually
4646
* refers to the class name.
4747
*/
4848
@ExperimentalAnvilApi
49-
@Suppress("unused")
49+
@Deprecated(
50+
message = "Use the createGeneratedFile with a `sourceFiles` argument instead.",
51+
replaceWith = ReplaceWith(
52+
expression = "createGeneratedFile(codeGenDir, packageName, fileName, content, sourceFile = TODO())",
53+
imports = ["com.squareup.anvil.compiler.api.createGeneratedFile"],
54+
),
55+
)
56+
@Suppress("UnusedReceiverParameter", "unused", "Deprecation")
5057
public fun CodeGenerator.createGeneratedFile(
5158
codeGenDir: File,
5259
packageName: String,
5360
fileName: String,
5461
content: String,
55-
): GeneratedFile {
62+
): GeneratedFile = GeneratedFile(
63+
file = writeFileContent(
64+
codeGenDir = codeGenDir,
65+
packageName = packageName,
66+
fileName = fileName,
67+
content = content,
68+
),
69+
content = content,
70+
)
71+
72+
public fun CodeGenerator.createGeneratedFile(
73+
codeGenDir: File,
74+
packageName: String,
75+
fileName: String,
76+
content: String,
77+
sourceFile: File,
78+
vararg additionalSourceFiles: File,
79+
): GeneratedFileWithSources = createGeneratedFile(
80+
codeGenDir = codeGenDir,
81+
packageName = packageName,
82+
fileName = fileName,
83+
content = content,
84+
sourceFiles = setOf(sourceFile, *additionalSourceFiles),
85+
)
86+
87+
@Suppress("UnusedReceiverParameter")
88+
public fun CodeGenerator.createGeneratedFile(
89+
codeGenDir: File,
90+
packageName: String,
91+
fileName: String,
92+
content: String,
93+
sourceFiles: Set<File>,
94+
): GeneratedFileWithSources = GeneratedFileWithSources(
95+
file = writeFileContent(
96+
codeGenDir = codeGenDir,
97+
packageName = packageName,
98+
fileName = fileName,
99+
content = content,
100+
),
101+
content = content,
102+
sourceFiles = sourceFiles,
103+
)
104+
105+
private fun writeFileContent(
106+
codeGenDir: File,
107+
packageName: String,
108+
fileName: String,
109+
content: String,
110+
): File {
56111
val directory = File(codeGenDir, packageName.replace('.', File.separatorChar))
57112
val file = File(directory, "$fileName.kt")
58113
check(file.parentFile.exists() || file.parentFile.mkdirs()) {
59114
"Could not generate package directory: ${file.parentFile}"
60115
}
61116
file.writeText(content)
62-
63-
return GeneratedFile(file, content)
117+
return file
64118
}

compiler-api/src/main/java/com/squareup/anvil/compiler/api/GeneratedFile.kt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,32 @@ import java.io.File
88
* @property file the [File] to generate.
99
* @property content the file contents to write to [file].
1010
*/
11-
public data class GeneratedFile(
12-
val file: File,
13-
val content: String,
11+
@Deprecated(
12+
message = "Use GeneratedFileWithSources instead.",
13+
replaceWith = ReplaceWith(
14+
expression = "GeneratedFileWithSources(file, content, setOf(TODO(\"Add some source files using <somePsiElement>.containingFileAsJavaFile()\")))",
15+
imports = [
16+
"com.squareup.anvil.compiler.api.GeneratedFileWithSources",
17+
"com.squareup.anvil.compiler.internal.containingFileAsJavaFile",
18+
],
19+
),
20+
level = DeprecationLevel.WARNING,
1421
)
22+
public data class GeneratedFile(
23+
override val file: File,
24+
override val content: String,
25+
) : FileWithContent
26+
27+
/** Represents a generated file that Anvil should eventually write. */
28+
public sealed interface FileWithContent : Comparable<FileWithContent> {
29+
/** The file to generate. */
30+
public val file: File
31+
32+
/** The text to write to [file]. */
33+
public val content: String
34+
35+
public operator fun component1(): File = file
36+
public operator fun component2(): String = content
37+
38+
override fun compareTo(other: FileWithContent): Int = file.compareTo(other.file)
39+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.squareup.anvil.compiler.api
2+
3+
import java.io.File
4+
5+
/**
6+
* Represents a generated file that Anvil should eventually write. The [sourceFiles] are the
7+
* files that triggered the generation of this file or are referenced by the generated file.
8+
* A modification to any of the [sourceFiles] will invalidate this generated file.
9+
*
10+
* All source files must be:
11+
* - absolute paths
12+
* - actual files (not directories)
13+
* - existent in the file system
14+
*
15+
* @property file the [File] to generate.
16+
* @property content the file contents to write to [file].
17+
* @property sourceFiles the source files used to generate this file.
18+
* @throws AnvilCompilationException if a [sourceFiles] is not an absolute file, doesn't exist, or is a directory
19+
* @throw AnvilCompilationException if a source file is the same as the generated [file]
20+
*/
21+
public class GeneratedFileWithSources(
22+
override val file: File,
23+
override val content: String,
24+
public val sourceFiles: Set<File>,
25+
) : FileWithContent {
26+
27+
init {
28+
fun require(condition: Boolean, lazyMessage: () -> String) {
29+
if (!condition) {
30+
throw AnvilCompilationException(
31+
"""
32+
|message:
33+
|${lazyMessage()}
34+
|
35+
|generated file:
36+
|$file
37+
|
38+
""".trimMargin(),
39+
)
40+
}
41+
}
42+
require(sourceFiles.none { it == file }) {
43+
"""
44+
|${this::class.simpleName} must not contain the generated file as a source file.
45+
|
46+
|source files:
47+
|${sourceFiles.sorted().joinToString("\n")}
48+
""".trimMargin()
49+
}
50+
require(sourceFiles.all { it.isFile && it.isAbsolute }) {
51+
52+
val notAbsolute = sourceFiles.filterNot { it.isAbsolute }.sorted()
53+
val notFiles = sourceFiles.filterNot { it.isFile }.sorted()
54+
55+
buildString {
56+
appendLine(
57+
"""
58+
All source files must be:
59+
- absolute paths
60+
- actual files (not directories)
61+
- existent in the file system
62+
""".trimIndent(),
63+
)
64+
if (notAbsolute.isNotEmpty()) {
65+
append(
66+
"""
67+
|
68+
|not absolute:
69+
|${notAbsolute.joinToString("\n")}
70+
""".trimMargin(),
71+
)
72+
}
73+
if (notFiles.isNotEmpty()) {
74+
append(
75+
"""
76+
|
77+
|not files:
78+
|${notFiles.joinToString("\n")}
79+
""".trimMargin(),
80+
)
81+
}
82+
}
83+
}
84+
}
85+
86+
public operator fun component3(): Set<File> = sourceFiles
87+
88+
override fun equals(other: Any?): Boolean {
89+
if (this === other) return true
90+
if (other !is GeneratedFileWithSources) return false
91+
92+
if (file != other.file) return false
93+
if (content != other.content) return false
94+
if (sourceFiles != other.sourceFiles) return false
95+
96+
return true
97+
}
98+
99+
override fun hashCode(): Int {
100+
var result = file.hashCode()
101+
result = 31 * result + content.hashCode()
102+
result = 31 * result + sourceFiles.hashCode()
103+
return result
104+
}
105+
106+
override fun toString(): String = buildString {
107+
appendLine("GeneratedFileWithSource(")
108+
appendLine(" file: $file")
109+
appendLine(" sourceFiles: ${sourceFiles.sorted()}")
110+
appendLine(" content: $content")
111+
appendLine(")")
112+
}
113+
}

0 commit comments

Comments
 (0)