Support fileAccessHistoryReportFile in KAPT

This change expanded KAPT to support a new param
'fileAccessHistoryReportFile', which reports all the classes used during
 annotation processing into a file, in the form of a list of URIs.

This is useful for build speed improvements described in https://engineering.fb.com/2017/11/09/android/rethinking-android-app-compilation-with-buck/.
Essentially, using the list of used classes, we can compile only
the dependencies that are really affected by the developer's
code changes and improve Kotlin build speed.

^KT-52853
This commit is contained in:
Jingbo Yang
2022-06-17 16:27:16 -04:00
committed by Space Team
parent 1b49ae3aab
commit 7a13173e6a
8 changed files with 145 additions and 22 deletions
@@ -47,7 +47,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge
init {
preregisterLog(context)
KaptJavaFileManager.preRegister(context)
KaptJavaFileManager.preRegister(context, options.fileReadHistoryReportFile != null)
@Suppress("LeakingThis")
preregisterTreeMaker(context)
@@ -43,7 +43,8 @@ class KaptOptions(
val processingClassLoader: ClassLoader?,
//construct new classloader for these processors instead of using one defined in processingClassLoader
val separateClassloaderForProcessors: Set<String>,
val processorsStatsReportFile: File?
val processorsStatsReportFile: File?,
val fileReadHistoryReportFile: File?
) : KaptFlags {
override fun get(flag: KaptFlag) = flags[flag]
@@ -74,6 +75,7 @@ class KaptOptions(
var mode: AptMode = AptMode.WITH_COMPILATION
var detectMemoryLeaks: DetectMemoryLeaksMode = DetectMemoryLeaksMode.DEFAULT
var processorsStatsReportFile: File? = null
var fileReadHistoryReportFile: File? = null
fun build(): KaptOptions {
val sourcesOutputDir = this.sourcesOutputDir ?: error("'sourcesOutputDir' must be set")
@@ -88,7 +90,8 @@ class KaptOptions(
mode, detectMemoryLeaks,
processingClassLoader = null,
separateClassloaderForProcessors = emptySet(),
processorsStatsReportFile = processorsStatsReportFile
processorsStatsReportFile = processorsStatsReportFile,
fileReadHistoryReportFile = fileReadHistoryReportFile
)
}
}
@@ -14,6 +14,7 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.kapt3.base.incremental.*
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
@@ -25,6 +26,7 @@ import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.JavaFileObject
import kotlin.collections.List
import kotlin.system.measureTimeMillis
import com.sun.tools.javac.util.List as JavacList
@@ -124,6 +126,10 @@ fun KaptContext.doAnnotationProcessing(
options.processorsStatsReportFile?.let { dumpProcessorStats(wrappedProcessors, it, logger::info) }
options.fileReadHistoryReportFile?.let {
dumpFileReadHistory(fileManager, it, logger::info)
}
if (logger.isVerbose) {
filer.displayState()
}
@@ -163,6 +169,12 @@ private fun dumpProcessorStats(wrappedProcessors: List<ProcessorWrapper>, apRepo
})
}
private fun dumpFileReadHistory(fileManager: KaptJavaFileManager, reportFile: File, logger: (String) -> Unit) {
logger("Dumping KAPT file read history to ${reportFile.absolutePath}")
reportFile.writeText(fileManager.renderFileReadHistory())
}
private fun reportIfRunningNonIncrementally(
listener: MentionedTypesTaskListener?,
cacheManager: JavaClassCacheManager?,
@@ -296,4 +308,4 @@ private fun KaptContext.initModulesIfNeeded(files: JavacList<JCTree.JCCompilatio
}
return files
}
}
@@ -9,12 +9,15 @@ import com.sun.tools.javac.file.JavacFileManager
import com.sun.tools.javac.main.Option
import com.sun.tools.javac.util.Context
import java.io.File
import java.io.InputStream
import java.io.Reader
import java.net.URI
import java.util.*
import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
import javax.tools.StandardLocation
import javax.tools.*
class KaptJavaFileManager(context: Context, private val shouldRecordFileRead: Boolean) : JavacFileManager(context, true, null) {
private val fileReadHistory = mutableSetOf<URI>()
class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, null) {
fun handleOptionJavac9(option: Option, value: String) {
val handleOptionMethod = JavacFileManager::class.java
.getMethod("handleOption", Option::class.java, String::class.java)
@@ -34,24 +37,70 @@ class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, nu
): MutableIterable<JavaFileObject> {
val originalList = super.list(location, packageName, kinds, recurse)
if (location == null
|| location != StandardLocation.CLASS_PATH
|| rootsToFilter.isEmpty()
|| typeToIgnore.isEmpty()
|| !filterThisPath(packageName)
) {
return originalList
}
val shouldPackageBeFiltered =
location != null &&
location == StandardLocation.CLASS_PATH &&
rootsToFilter.isNotEmpty() &&
typeToIgnore.isNotEmpty() &&
filterThisPath(packageName)
val filteredList = LinkedList<JavaFileObject>()
for (file in originalList)
if (!shouldBeFiltered(packageName, file)) {
filteredList.add(file)
if (!shouldPackageBeFiltered || !shouldBeFiltered(packageName, file)) {
filteredList.add(wrapWithReadMonitoringIfNeeded(location, file) as JavaFileObject)
}
return filteredList
}
override fun getJavaFileForInput(location: JavaFileManager.Location?, className: String?, kind: JavaFileObject.Kind?): JavaFileObject? {
val file = super.getJavaFileForInput(location, className, kind)
return wrapWithReadMonitoringIfNeeded(location, file) as JavaFileObject?
}
override fun getJavaFileForOutput(
location: JavaFileManager.Location?,
className: String?,
kind: JavaFileObject.Kind?,
sibling: FileObject?
): JavaFileObject? {
val file = super.getJavaFileForOutput(location, className, kind, sibling)
return wrapWithReadMonitoringIfNeeded(location, file) as JavaFileObject?
}
override fun getFileForInput(location: JavaFileManager.Location?, packageName: String?, relativeName: String?): FileObject? {
val file = super.getFileForInput(location, packageName, relativeName)
return wrapWithReadMonitoringIfNeeded(location, file)
}
override fun getFileForOutput(
location: JavaFileManager.Location?,
packageName: String?,
relativeName: String?,
sibling: FileObject?
): FileObject? {
val file = super.getFileForOutput(location, packageName, relativeName, sibling)
return wrapWithReadMonitoringIfNeeded(location, file)
}
/** javac does not play nice with wrapped file objects in this method; so we unwrap */
override fun inferBinaryName(location: JavaFileManager.Location?, file: JavaFileObject): String? =
super.inferBinaryName(location, unwrapObject(file) as JavaFileObject)
/** javac does not play nice with wrapped file objects in this method; so we unwrap */
override fun isSameFile(a: FileObject, b: FileObject): Boolean {
return super.isSameFile(unwrapObject(a), unwrapObject(b))
}
private fun wrapWithReadMonitoringIfNeeded(location: JavaFileManager.Location?, file: FileObject?) =
if (shouldRecordFileRead && location != StandardLocation.ANNOTATION_PROCESSOR_PATH && file != null && file is JavaFileObject)
ReadMonitoredJavaFileObject(file)
else file
private fun unwrapObject(file: FileObject): FileObject =
if (file is ReadMonitoredJavaFileObject) file.getJavaFileObject() else file
fun renderFileReadHistory() = fileReadHistory.sorted().joinToString("\n")
private fun filterThisPath(packageName: String?): Boolean {
packageName ?: return false
@@ -72,9 +121,32 @@ class KaptJavaFileManager(context: Context) : JavacFileManager(context, true, nu
}
}
private inner class ReadMonitoredJavaFileObject(innerFile: JavaFileObject) : ForwardingJavaFileObject<JavaFileObject>(innerFile) {
fun getJavaFileObject(): JavaFileObject = fileObject
override fun toString(): String = fileObject.name
override fun openInputStream(): InputStream {
recordFileRead()
return super.openInputStream()
}
override fun openReader(ignoreEncodingErrors: Boolean): Reader {
recordFileRead()
return super.openReader(ignoreEncodingErrors)
}
override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence {
recordFileRead()
return super.getCharContent(ignoreEncodingErrors)
}
private fun recordFileRead() {
fileReadHistory.add(fileObject.toUri())
}
}
companion object {
internal fun preRegister(context: Context) {
context.put(JavaFileManager::class.java, Context.Factory<JavaFileManager> { KaptJavaFileManager(it) })
internal fun preRegister(context: Context, shouldRecordFileRead: Boolean) {
context.put(JavaFileManager::class.java, Context.Factory { KaptJavaFileManager(it, shouldRecordFileRead) })
}
}
}
@@ -58,7 +58,12 @@ class JavaKaptContextTest {
)
}
private fun doAnnotationProcessing(javaSourceFile: File, processor: IncrementalProcessor, outputDir: File) {
private fun doAnnotationProcessing(
javaSourceFile: File,
processor: IncrementalProcessor,
outputDir: File,
fileReadOutput: File? = null
) {
val options = KaptOptions.Builder().apply {
projectBaseDir = javaSourceFile.parentFile
@@ -67,6 +72,8 @@ class JavaKaptContextTest {
stubsOutputDir = outputDir
incrementalDataOutputDir = outputDir
fileReadHistoryReportFile = fileReadOutput
flags.add(KaptFlag.MAP_DIAGNOSTIC_LOCATIONS)
detectMemoryLeaks = DetectMemoryLeaksMode.NONE
}.build()
@@ -86,6 +93,26 @@ class JavaKaptContextTest {
}
}
@Test
fun testDumpFileReadHistory() {
val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile()
val fileReadOutputFile = File.createTempFile("kapt_read_history", ".txt")
try {
doAnnotationProcessing(
File(TEST_DATA_DIR, "Simple.java"),
simpleProcessor(),
sourceOutputDir,
fileReadOutput = fileReadOutputFile
)
assertTrue(fileReadOutputFile.exists())
assertTrue(fileReadOutputFile.readText().contains("generated/MyMethodMyAnnotation.java"))
assertTrue(fileReadOutputFile.readText().contains("java/lang/Enum.class"))
} finally {
sourceOutputDir.deleteRecursively()
fileReadOutputFile.delete()
}
}
@Test
fun testException() {
val exceptionMessage = "Here we are!"
@@ -187,6 +187,13 @@ enum class KaptCliOption(
cliToolOption = CliToolOption("-Kapt-dump-processor-stats", VALUE)
),
DUMP_FILE_READ_HISTORY(
"dumpFileReadHistory",
"<path>",
"Dump list of files read by processors to the specified file",
cliToolOption = CliToolOption("-Kapt-dump-file-read-history", VALUE)
),
STRICT_MODE_OPTION(
"strict",
"true | false",
@@ -128,6 +128,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor {
SHOW_PROCESSOR_STATS -> setFlag(KaptFlag.SHOW_PROCESSOR_STATS, value)
DUMP_PROCESSOR_STATS -> processorsStatsReportFile = File(value)
DUMP_FILE_READ_HISTORY -> fileReadHistoryReportFile = File(value)
INCLUDE_COMPILE_CLASSPATH -> setFlag(KaptFlag.INCLUDE_COMPILE_CLASSPATH, value)
DETECT_MEMORY_LEAKS_OPTION -> setSelector(enumValues<DetectMemoryLeaksMode>(), value) { detectMemoryLeaks = it }