Remove source annotations when copy class with kapt2

Writing source annotations enables incremental compilation for kapt2.
However they are not needed in bytecode, so we remove them when
copying classes.

# Conflicts:
#	compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt
#	compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java
This commit is contained in:
Alexey Tsvetkov
2016-09-06 15:55:03 +03:00
parent 5534350fd6
commit 6ebb50751c
25 changed files with 377 additions and 78 deletions
@@ -24,13 +24,10 @@ import org.jetbrains.kotlin.modules.TargetId
class IncrementalCompilationComponentsImpl(
private val caches: Map<TargetId, IncrementalCache>,
private val lookupTracker: LookupTracker,
private val sourceRetentionAnnotationHandler: SourceRetentionAnnotationHandler?
private val lookupTracker: LookupTracker
): IncrementalCompilationComponents {
override fun getIncrementalCache(target: TargetId): IncrementalCache =
caches[target] ?: throw Exception("Incremental cache for target ${target.name} not found")
override fun getLookupTracker(): LookupTracker = lookupTracker
override fun getSourceRetentionAnnotationHandler() = sourceRetentionAnnotationHandler
}
@@ -67,24 +67,21 @@ fun makeModuleFile(name: String, isTest: Boolean, outputDir: File, sourcesToComp
return scriptFile
}
fun makeCompileServices(
incrementalCaches: Map<TargetId, IncrementalCache>,
lookupTracker: LookupTracker,
compilationCanceledStatus: CompilationCanceledStatus?
) = makeCompileServices(incrementalCaches, lookupTracker, compilationCanceledStatus, null)
fun makeCompileServices(
incrementalCaches: Map<TargetId, IncrementalCache>,
lookupTracker: LookupTracker,
compilationCanceledStatus: CompilationCanceledStatus?,
sourceRetentionAnnotationHandler: SourceRetentionAnnotationHandler?
sourceRetentionAnnotationHandler: SourceRetentionAnnotationHandler? = null
): Services =
with(Services.Builder()) {
register(IncrementalCompilationComponents::class.java,
IncrementalCompilationComponentsImpl(incrementalCaches, lookupTracker, sourceRetentionAnnotationHandler))
IncrementalCompilationComponentsImpl(incrementalCaches, lookupTracker))
compilationCanceledStatus?.let {
register(CompilationCanceledStatus::class.java, it)
}
sourceRetentionAnnotationHandler?.let {
register(SourceRetentionAnnotationHandler::class.java, it)
}
build()
}
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
import org.jetbrains.kotlin.compiler.plugin.PluginCliOptionProcessingException
import org.jetbrains.kotlin.compiler.plugin.cliPluginUsageString
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHandler
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
@@ -300,6 +301,10 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
configuration.put(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS, components)
}
}
services.get(SourceRetentionAnnotationHandler::class.java)?.let { handler ->
configuration.put(JVMConfigurationKeys.SOURCE_RETENTION_ANNOTATION_HANDLER, handler)
}
}
/**
@@ -32,6 +32,4 @@ class RemoteIncrementalCompilationComponentsClient(val facade: CompilerCallbackS
override fun getIncrementalCache(target: TargetId): IncrementalCache = RemoteIncrementalCacheClient(facade, target, profiler)
override fun getLookupTracker(): LookupTracker = remoteLookupTrackerClient
override fun getSourceRetentionAnnotationHandler() = null
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.config;
import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHandler;
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents;
import org.jetbrains.kotlin.modules.Module;
import org.jetbrains.kotlin.script.KotlinScriptDefinition;
@@ -64,6 +65,9 @@ public class JVMConfigurationKeys {
public static final CompilerConfigurationKey<File> MODULE_XML_FILE =
CompilerConfigurationKey.create("path to module.xml");
public static final CompilerConfigurationKey<SourceRetentionAnnotationHandler> SOURCE_RETENTION_ANNOTATION_HANDLER =
CompilerConfigurationKey.create("source retention annotation handler");
public static final CompilerConfigurationKey<String> DECLARATIONS_JSON_PATH =
CompilerConfigurationKey.create("path to declarations output");
@@ -23,5 +23,4 @@ import org.jetbrains.kotlin.modules.TargetId
interface IncrementalCompilationComponents {
fun getIncrementalCache(target: TargetId): IncrementalCache
fun getLookupTracker(): LookupTracker
fun getSourceRetentionAnnotationHandler(): SourceRetentionAnnotationHandler?
}
@@ -414,16 +414,20 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
sourceRetentionAnnotationHandler: SourceRetentionAnnotationHandler?,
context: CompileContext
): CompilerEnvironment {
val compilerServices = Services.Builder()
.register(IncrementalCompilationComponents::class.java,
IncrementalCompilationComponentsImpl(incrementalCaches.mapKeys { TargetId(it.key) },
lookupTracker, sourceRetentionAnnotationHandler))
.register(CompilationCanceledStatus::class.java, object : CompilationCanceledStatus {
override fun checkCanceled() {
if (context.cancelStatus.isCanceled) throw CompilationCanceledException()
}
})
.build()
val compilerServices = with(Services.Builder()) {
register(IncrementalCompilationComponents::class.java,
IncrementalCompilationComponentsImpl(incrementalCaches.mapKeys { TargetId(it.key) },
lookupTracker))
register(CompilationCanceledStatus::class.java, object : CompilationCanceledStatus {
override fun checkCanceled() {
if (context.cancelStatus.isCanceled) throw CompilationCanceledException()
}
})
sourceRetentionAnnotationHandler?.let {
register(SourceRetentionAnnotationHandler::class.java, it)
}
build()
}
return CompilerEnvironment.getEnvironmentFor(
PathUtil.getKotlinPathsForJpsPluginOrJpsTests(),
@@ -22,7 +22,7 @@ import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
class ExampleSubplugin : KotlinGradleSubplugin {
class ExampleSubplugin : KotlinGradleSubplugin<AbstractCompile> {
override fun isApplicable(project: Project, task: AbstractCompile): Boolean {
return true
@@ -22,12 +22,12 @@ import org.gradle.api.tasks.compile.AbstractCompile
class SubpluginOption(val key: String, val value: String)
interface KotlinGradleSubplugin {
fun isApplicable(project: Project, task: AbstractCompile): Boolean
interface KotlinGradleSubplugin<KotlinCompile : AbstractCompile> {
fun isApplicable(project: Project, task: KotlinCompile): Boolean
fun apply(
project: Project,
kotlinCompile: AbstractCompile,
kotlinCompile: KotlinCompile,
javaCompile: AbstractCompile,
variantData: Any?,
javaSourceSet: SourceSet?
@@ -0,0 +1,52 @@
package org.jetbrains.kotlin.annotation
import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHandler
import java.io.*
import java.util.*
class SourceAnnotationsRegistry(private val file: File) : SourceRetentionAnnotationHandler {
private val mutableAnnotations: MutableSet<String> by lazy { readAnnotations() }
val annotations: Set<String>
get() = mutableAnnotations
override fun register(internalName: String) {
mutableAnnotations.add(internalName)
}
fun clear() {
mutableAnnotations.clear()
file.delete()
}
fun flush() {
if (mutableAnnotations.isEmpty()) {
file.delete()
return
}
if (!file.exists()) {
file.parentFile.mkdirs()
file.createNewFile()
}
ObjectOutputStream(BufferedOutputStream(file.outputStream())).use { out ->
out.writeInt(mutableAnnotations.size)
mutableAnnotations.forEach { out.writeUTF(it) }
}
}
private fun readAnnotations(): MutableSet<String> {
val result = HashSet<String>()
if (!file.exists()) return result
ObjectInputStream(BufferedInputStream(file.inputStream())).use { input ->
val size = input.readInt()
repeat(size) {
result.add(input.readUTF())
}
}
return result
}
}
@@ -12,6 +12,7 @@ import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.jetbrains.kotlin.annotation.AnnotationFileUpdater
import org.jetbrains.kotlin.annotation.SourceAnnotationsRegistry
import org.jetbrains.kotlin.build.GeneratedFile
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode
@@ -41,6 +42,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHandler
import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
@@ -153,7 +155,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
private val sourceRoots = HashSet<File>()
// lazy because name is probably not available when constructor is called
private val taskBuildDirectory: File by lazy { File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name) }
val taskBuildDirectory: File by lazy { File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name) }
private val cacheDirectory: File by lazy { File(taskBuildDirectory, CACHES_DIR_NAME) }
private val dirtySourcesSinceLastTimeFile: File by lazy { File(taskBuildDirectory, DIRTY_SOURCES_FILE_NAME) }
private val lastBuildInfoFile: File by lazy { File(taskBuildDirectory, LAST_BUILD_INFO_FILE_NAME) }
@@ -180,6 +182,8 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
val pluginOptions = CompilerPluginOptions()
var artifactDifferenceRegistry: ArtifactDifferenceRegistry? = null
var artifactFile: File? = null
// created only if kapt2 is active
var sourceAnnotationsRegistry: SourceAnnotationsRegistry? = null
override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) {
logger.kotlinDebug("args.freeArgs = ${args.freeArgs}")
@@ -413,6 +417,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details")
ExitCode.SCRIPT_EXECUTION_ERROR -> throw GradleException("Script execution error. See log for more details")
ExitCode.OK -> {
sourceAnnotationsRegistry?.flush()
cacheVersions.forEach { it.saveIfNeeded() }
logger.kotlinInfo("Compilation succeeded")
}
@@ -540,6 +545,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
args.module = moduleFile.absolutePath
val outputItemCollector = OutputItemsCollectorImpl()
val messageCollector = GradleMessageCollector(logger, outputItemCollector)
sourceAnnotationsRegistry?.clear()
try {
val incrementalCaches = makeIncrementalCachesMap(targets, { listOf<TargetId>() }, getIncrementalCache, { this })
@@ -549,7 +555,8 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
}
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
val exitCode = compiler.exec(messageCollector, makeCompileServices(incrementalCaches, lookupTracker, compilationCanceledStatus), args)
val compileServices = makeCompileServices(incrementalCaches, lookupTracker, compilationCanceledStatus, sourceAnnotationsRegistry)
val exitCode = compiler.exec(messageCollector, compileServices, args)
return CompileChangedResults(
exitCode,
outputItemCollector.generatedFiles(
@@ -584,9 +591,17 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
args.module = moduleFile.absolutePath
val messageCollector = GradleMessageCollector(logger)
sourceAnnotationsRegistry?.clear()
val services = with (Services.Builder()) {
sourceAnnotationsRegistry?.let { handler ->
register(SourceRetentionAnnotationHandler::class.java, handler)
}
build()
}
try {
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
return compiler.exec(messageCollector, Services.EMPTY, args)
return compiler.exec(messageCollector, services, args)
}
finally {
moduleFile.delete()
@@ -0,0 +1,61 @@
package org.jetbrains.kotlin.bytecode
import org.jetbrains.org.objectweb.asm.*
import java.io.File
import java.util.*
class AnnotationsRemover(annotations: Iterable<String>) {
private val annotations = annotations.mapTo(HashSet()) { "L$it;" }
fun transformClassFile(inputFile: File, outputFile: File) {
val bytes = inputFile.readBytes()
val reader = ClassReader(bytes)
val classWriter = ClassWriter(0)
val visitor = ClassAnnotationRemover(classWriter)
reader.accept(visitor, 0)
outputFile.writeBytes(classWriter.toByteArray())
}
inner class ClassAnnotationRemover(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM5, classVisitor) {
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitAnnotation(desc, visible) }
override fun visitTypeAnnotation(typeRef: Int, typePath: TypePath?, desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitTypeAnnotation(typeRef, typePath, desc, visible) }
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor {
val methodVisitor = super.visitMethod(access, name, desc, signature, exceptions)
return MethodAnnotationRemover(methodVisitor)
}
override fun visitField(access: Int, name: String?, desc: String?, signature: String?, value: Any?): FieldVisitor {
val fieldVisitor = super.visitField(access, name, desc, signature, value)
return FieldAnnotationRemover(fieldVisitor)
}
}
inner class MethodAnnotationRemover(methodVisitor: MethodVisitor) : MethodVisitor(Opcodes.ASM5, methodVisitor) {
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitAnnotation(desc, visible) }
override fun visitTypeAnnotation(typeRef: Int, typePath: TypePath?, desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitTypeAnnotation(typeRef, typePath, desc, visible) }
override fun visitParameterAnnotation(parameter: Int, desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitParameterAnnotation(parameter, desc, visible) }
}
inner class FieldAnnotationRemover(fieldVisitor: FieldVisitor) : FieldVisitor(Opcodes.ASM5, fieldVisitor) {
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitAnnotation(desc, visible) }
override fun visitTypeAnnotation(typeRef: Int, typePath: TypePath?, desc: String?, visible: Boolean): AnnotationVisitor? =
checkAnnotation(desc) { super.visitTypeAnnotation(typeRef, typePath, desc, visible) }
}
private inline fun checkAnnotation(desc: String?, default: () -> AnnotationVisitor?): AnnotationVisitor? {
if (desc in annotations) return null
return default()
}
}
@@ -26,6 +26,7 @@ import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.w3c.dom.Document
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
@@ -36,13 +37,13 @@ class AndroidExtensionsSubpluginIndicator : Plugin<Project> {
override fun apply(target: Project?) {}
}
class AndroidSubplugin : KotlinGradleSubplugin {
class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private companion object {
@Volatile
var migrateWarningReported: Boolean = false
}
override fun isApplicable(project: Project, task: AbstractCompile): Boolean {
override fun isApplicable(project: Project, task: KotlinCompile): Boolean {
try {
project.extensions.getByName("android") as? BaseExtension ?: return false
} catch (e: UnknownDomainObjectException) {
@@ -61,7 +62,7 @@ class AndroidSubplugin : KotlinGradleSubplugin {
override fun apply(
project: Project,
kotlinCompile: AbstractCompile,
kotlinCompile: KotlinCompile,
javaCompile: AbstractCompile,
variantData: Any?,
javaSourceSet: SourceSet?
@@ -25,9 +25,11 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.annotation.SourceAnnotationsRegistry
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
// apply plugin: 'kotlin-kapt2'
@@ -40,7 +42,7 @@ class Kapt2GradleSubplugin : Plugin<Project> {
}
// Subplugin for the Kotlin Gradle plugin
class Kapt2KotlinGradleSubplugin : KotlinGradleSubplugin {
class Kapt2KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
companion object {
private val VERBOSE_OPTION_NAME = "kapt.verbose"
@@ -49,7 +51,7 @@ class Kapt2KotlinGradleSubplugin : KotlinGradleSubplugin {
}
}
override fun isApplicable(project: Project, task: AbstractCompile) = Kapt2GradleSubplugin.isEnabled(project)
override fun isApplicable(project: Project, task: KotlinCompile) = Kapt2GradleSubplugin.isEnabled(project)
fun getKaptGeneratedDir(project: Project, sourceSetName: String): File {
return File(project.project.buildDir, "generated/source/kapt2/$sourceSetName")
@@ -61,7 +63,7 @@ class Kapt2KotlinGradleSubplugin : KotlinGradleSubplugin {
override fun apply(
project: Project,
kotlinCompile: AbstractCompile,
kotlinCompile: KotlinCompile,
javaCompile: AbstractCompile,
variantData: Any?,
javaSourceSet: SourceSet?
@@ -121,7 +123,10 @@ class Kapt2KotlinGradleSubplugin : KotlinGradleSubplugin {
if (project.hasProperty(VERBOSE_OPTION_NAME) && project.property(VERBOSE_OPTION_NAME) == "true") {
pluginOptions += SubpluginOption("verbose", "true")
}
val annotationsFile = File(kotlinCompile.taskBuildDirectory, "source-annotations.txt")
kotlinCompile.sourceAnnotationsRegistry = SourceAnnotationsRegistry(annotationsFile)
val incrementalCompilationDataFile = File(project.buildDir, "tmp/kapt2/$sourceSetName/incrementalData.txt")
pluginOptions += SubpluginOption("incrementalData", incrementalCompilationDataFile.absolutePath)
@@ -459,13 +459,15 @@ private fun createSyncOutputTask(
variantName: String
) {
// if kotlinAfterJavaTask is not null then kotlinTask compiles stubs, so don't sync them
val kotlinDir = (kotlinAfterJavaTask ?: kotlinTask).destinationDir
val kotlinCompile = kotlinAfterJavaTask ?: kotlinTask
val kotlinDir = kotlinCompile.destinationDir
val javaDir = javaTask.destinationDir
val taskName = "copy${variantName.capitalize()}KotlinClasses"
val syncTask = project.tasks.create(taskName, SyncOutputTask::class.java)
syncTask.kotlinOutputDir = kotlinDir
syncTask.javaOutputDir = javaDir
syncTask.kotlinTask = kotlinCompile
kotlinTask.javaOutputDir = javaDir
kotlinAfterJavaTask?.javaOutputDir = javaDir
@@ -478,7 +480,8 @@ private fun createSyncOutputTask(
private fun loadSubplugins(project: Project): SubpluginEnvironment {
try {
val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader).toList()
val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader)
.map { @Suppress("UNCHECKED_CAST") (it as KotlinGradleSubplugin<KotlinCompile>) }
fun Project.getResolvedArtifacts() = buildscript.configurations.getByName("classpath")
.resolvedConfiguration.resolvedArtifacts
@@ -489,7 +492,7 @@ private fun loadSubplugins(project: Project): SubpluginEnvironment {
resolvedClasspathArtifacts += rootProject.getResolvedArtifacts()
}
val subpluginClasspaths = hashMapOf<KotlinGradleSubplugin, List<File>>()
val subpluginClasspaths = hashMapOf<KotlinGradleSubplugin<KotlinCompile>, List<File>>()
for (subplugin in subplugins) {
val file = resolvedClasspathArtifacts
@@ -511,8 +514,8 @@ private fun loadSubplugins(project: Project): SubpluginEnvironment {
}
class SubpluginEnvironment(
val subpluginClasspaths: Map<KotlinGradleSubplugin, List<File>>,
val subplugins: List<KotlinGradleSubplugin>
val subpluginClasspaths: Map<KotlinGradleSubplugin<KotlinCompile>, List<File>>,
val subplugins: List<KotlinGradleSubplugin<KotlinCompile>>
) {
fun addSubpluginArguments(
@@ -23,10 +23,11 @@ import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.api.tasks.incremental.InputFileDetails
import org.jetbrains.kotlin.bytecode.AnnotationsRemover
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import org.jetbrains.kotlin.incremental.md5
import org.jetbrains.org.objectweb.asm.*
import java.io.*
import java.util.*
import kotlin.properties.Delegates
@@ -56,6 +57,11 @@ open class SyncOutputTask : DefaultTask() {
@get:InputFiles
var kotlinOutputDir: File by Delegates.notNull()
var javaOutputDir: File by Delegates.notNull()
var kotlinTask: KotlinCompile by Delegates.notNull()
private val sourceAnnotations: Set<String> by lazy {
kotlinTask.sourceAnnotationsRegistry?.annotations ?: emptySet()
}
private val annotationsRemover by lazy { AnnotationsRemover(sourceAnnotations) }
// OutputDirectory needed for task to be incremental
@get:OutputDirectory
@@ -130,7 +136,13 @@ open class SyncOutputTask : DefaultTask() {
if (!fileInKotlinDir.isFile) return
fileInJavaDir.parentFile.mkdirs()
fileInKotlinDir.copyTo(fileInJavaDir, overwrite = true)
if (sourceAnnotations.isEmpty()) {
fileInKotlinDir.copyTo(fileInJavaDir, overwrite = true)
}
else {
annotationsRemover.transformClassFile(fileInKotlinDir, fileInJavaDir)
}
timestamps[fileInJavaDir] = fileInJavaDir.lastModified()
logger.kotlinDebug {
@@ -0,0 +1,108 @@
package org.jetbrains.kotlin.bytecode
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil.createTempDirectory
import org.jetbrains.kotlin.gradle.util.checkBytecodeContains
import org.jetbrains.kotlin.gradle.util.checkBytecodeNotContains
import org.junit.After
import org.junit.Test
import org.junit.Assert.*
import org.junit.Before
import java.io.*
import kotlin.properties.Delegates
class AnnotationsRemoverTest {
private var workingDir: File by Delegates.notNull()
@Before
fun setUp() {
workingDir = createTempDirectory(AnnotationsRemoverTest::class.java.simpleName, null)
}
@After
fun tearDown() {
workingDir.deleteRecursively()
}
@Test
fun testRemoveAnnotations() {
// initial build
val sourceDir = File(workingDir, "src").apply { mkdirs() }
val annotationsKt = File(sourceDir, "annotations.kt").apply {
writeText("""
package foo
annotation class Ann1
annotation class Ann2
annotation class Ann3
annotation class Ann4
annotation class Ann5
annotation class NotRemovableAnn1
annotation class NotRemovableAnn2
annotation class NotRemovableAnn3
annotation class NotRemovableAnn4
annotation class NotRemovableAnn5
""".trimIndent())
}
File(sourceDir, "A.kt").apply {
writeText("""
import foo.*
@Ann1
@NotRemovableAnn1
class A {
@get:Ann2
@field:Ann3
@get:NotRemovableAnn2
@field:NotRemovableAnn3
val i = 10
@Ann4
@NotRemovableAnn4
fun m(@Ann5 @NotRemovableAnn5 x: Int) {}
}
""".trimIndent())
}
val annClassRegex = "annotation class (Ann\\d)".toRegex()
val annotationsToRemove = annClassRegex.findAll(annotationsKt.readText()).toList().map { "foo/${it.groupValues[1]}" }
assertEquals(5, annotationsToRemove.size)
val notRemovableAnnClassRegex = "annotation class (NotRemovableAnn\\d)".toRegex()
val notRemovableAnns = notRemovableAnnClassRegex.findAll(annotationsKt.readText()).toList().map { "foo/${it.groupValues[1]}" }
assertEquals(5, notRemovableAnns.size)
val outDir = File(workingDir, "out").apply { mkdirs() }
compileAll(sourceDir, outDir)
val aClass = File(outDir, "A.class")
assert(aClass.exists()) { "$aClass does not exist" }
checkBytecodeContains(aClass, annotationsToRemove)
// remove annotations
val transformedOut = File(workingDir, "transformed").apply { mkdirs() }
val aTransformedClass = File(transformedOut, "A.class")
val remover = AnnotationsRemover(annotationsToRemove)
remover.transformClassFile(aClass, aTransformedClass)
checkBytecodeNotContains(aTransformedClass, annotationsToRemove)
checkBytecodeContains(aTransformedClass, notRemovableAnns)
}
private fun compileAll(inputDir: File, outputDir: File) {
val ktFiles = inputDir.walk()
.filter { it.isFile && it.extension.toLowerCase() == "kt" }
.map { it.absolutePath }
.toList().toTypedArray()
val byteOut = ByteArrayOutputStream()
val exitCode = PrintStream(byteOut).use { err ->
K2JVMCompiler().exec(err, *ktFiles, "-d", outputDir.absolutePath)
}
if (exitCode != ExitCode.OK) {
System.err.print(byteOut.toString())
}
assertEquals(ExitCode.OK, exitCode)
}
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleBuildServices
import org.jetbrains.kotlin.gradle.util.checkBytecodeNotContains
import org.jetbrains.kotlin.gradle.util.createGradleCommand
import org.jetbrains.kotlin.gradle.util.runProcess
import org.junit.After
@@ -162,6 +163,14 @@ abstract class BaseGradleIT {
return this
}
fun CompiledProject.assertClassFilesNotContain(dir: File, vararg strings: String) {
val classFiles = dir.walk().filter { it.isFile && it.extension.toLowerCase() == "class" }
for (cf in classFiles) {
checkBytecodeNotContains(cf, strings.toList())
}
}
fun CompiledProject.assertSubstringCount(substring: String, expectedCount: Int) {
val actualCount = Pattern.quote(substring).toRegex().findAll(output).count()
assertEquals(expectedCount, actualCount, "Number of occurrences in output for substring '$substring'")
@@ -16,10 +16,9 @@
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.*
import org.junit.Test
import java.io.File
import java.io.FileFilter
class Kapt2IT: BaseGradleIT() {
companion object {
@@ -56,6 +55,7 @@ class Kapt2IT: BaseGradleIT() {
assertFileExists("build/classes/main/example/BinaryAnnotatedTestClassGenerated.class")
assertFileExists("build/classes/main/example/RuntimeAnnotatedTestClassGenerated.class")
assertContains("example.JavaTest PASSED")
assertClassFilesNotContain(File(project.projectDir, "build/classes"), "ExampleSourceAnnotation")
}
project.build("build") {
@@ -69,24 +69,32 @@ class Kapt2IT: BaseGradleIT() {
fun testSimpleWithIC() {
val options = defaultBuildOptions().copy(incremental = true)
val project = Project("simple", GRADLE_VERSION, directoryPrefix = "kapt2")
val classesDir = File(project.projectDir, "build/classes")
project.build("build", options = options) {
assertSuccessful()
assertKaptSuccessful()
assertContains(":compileKotlin")
assertContains(":compileJava")
assertClassFilesNotContain(classesDir, "ExampleSourceAnnotation")
}
val files = listOf("InternalDummy.kt", "test.kt")
kotlin.repeat(2) { i ->
project.projectDir.getFileByName(files[i]).appendText(" ")
project.projectDir.getFilesByNames("InternalDummy.kt", "test.kt").forEach { it.appendText(" ") }
project.build("build", options = options) {
assertSuccessful()
assertKaptSuccessful()
assertContains(":compileKotlin")
assertContains(":compileJava")
assertClassFilesNotContain(classesDir, "ExampleSourceAnnotation")
}
project.build("build", options = options) {
assertSuccessful()
assertKaptSuccessful()
assertContains(":compileKotlin")
assertContains(":compileJava")
}
// emulating wipe by android plugin's IncrementalSafeguardTask
classesDir.deleteRecursively()
project.build("build", options = options) {
assertSuccessful()
assertContains(":compileKotlin UP-TO-DATE")
assertFileExists("build/classes/main/example/TestClass.class")
assertClassFilesNotContain(classesDir, "ExampleSourceAnnotation")
}
}
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.util.allJavaFiles
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
import java.io.File
class KaptIT: BaseGradleIT() {
@@ -32,6 +33,7 @@ class KaptIT: BaseGradleIT() {
assertFileExists("build/classes/main/example/RuntimeAnnotatedTestClassGenerated.class")
assertContains("example.JavaTest PASSED")
assertContains("example.KotlinTest PASSED")
assertClassFilesNotContain(File(project.projectDir, "build/classes"), "ExampleSourceAnnotation")
}
// clean build is important
@@ -0,0 +1,28 @@
package org.jetbrains.kotlin.gradle.util
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
fun classFileBytecodeString(classFile: File): String {
val out = StringWriter()
val traceVisitor = TraceClassVisitor(PrintWriter(out))
ClassReader(classFile.readBytes()).accept(traceVisitor, 0)
return out.toString()
}
fun checkBytecodeContains(classFile: File, strings: Iterable<String>) {
val bytecode = classFileBytecodeString(classFile)
for (string in strings) {
assert(bytecode.contains(string)) { "Bytecode should contain '$string':\n$bytecode" }
}
}
fun checkBytecodeNotContains(classFile: File, strings: Iterable<String>) {
val bytecode = classFileBytecodeString(classFile)
for (string in strings) {
assert(!bytecode.contains(string)) { "Bytecode should NOT contain '$string':\n$bytecode" }
}
}
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.APPEND_JAVA_SOURCE_ROOTS_HANDLER_KEY
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHandler
import org.jetbrains.kotlin.java.model.elements.JeTypeElement
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
@@ -51,10 +52,10 @@ class ClasspathBasedAnnotationProcessingExtension(
javaSourceRoots: List<File>,
verboseOutput: Boolean,
incrementalDataFile: File?,
incrementalCompilationComponents: IncrementalCompilationComponents?
sourceRetentionAnnotationHandler: SourceRetentionAnnotationHandler?
) : AbstractAnnotationProcessingExtension(generatedSourcesOutputDir,
classesOutputDir, javaSourceRoots, verboseOutput,
incrementalDataFile, incrementalCompilationComponents) {
incrementalDataFile, sourceRetentionAnnotationHandler) {
override fun loadAnnotationProcessors(): List<Processor> {
val classLoader = URLClassLoader(annotationProcessingClasspath.map { it.toURI().toURL() }.toTypedArray())
return ServiceLoader.load(Processor::class.java, classLoader).toList()
@@ -67,7 +68,7 @@ abstract class AbstractAnnotationProcessingExtension(
val javaSourceRoots: List<File>,
val verboseOutput: Boolean,
val incrementalDataFile: File? = null,
val incrementalCompilationComponents: IncrementalCompilationComponents? = null
val sourceRetentionAnnotationHandler: SourceRetentionAnnotationHandler? = null
) : AnalysisCompletedHandlerExtension {
private companion object {
val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
@@ -170,7 +171,7 @@ abstract class AbstractAnnotationProcessingExtension(
}
val firstRoundAnnotations = RoundAnnotations(
incrementalCompilationComponents?.getSourceRetentionAnnotationHandler(),
sourceRetentionAnnotationHandler,
bindingContext,
createTypeMapper())
@@ -122,11 +122,11 @@ class AnnotationProcessingComponentRegistrar : ComponentRegistrar {
// Annotations with the "SOURCE" retention will be written to class files
project.putUserData(IS_KAPT2_ENABLED_KEY, true)
val incrementalCompilationComponents = configuration[JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS]
val sourceRetentionAnnotationHandler = configuration[JVMConfigurationKeys.SOURCE_RETENTION_ANNOTATION_HANDLER]
val annotationProcessingExtension = ClasspathBasedAnnotationProcessingExtension(
classpath, generatedOutputDirFile, classesOutputDir, javaRoots, verboseOutput,
incrementalDataFile, incrementalCompilationComponents)
incrementalDataFile, sourceRetentionAnnotationHandler)
AnalysisCompletedHandlerExtension.registerExtension(project, annotationProcessingExtension)
}
@@ -43,7 +43,7 @@ import javax.lang.model.element.*
class AnnotationProcessingExtensionForTests(
val processors: List<Processor>
) : AbstractAnnotationProcessingExtension(createTempDir(), createTempDir(), listOf(), true,
createIncrementalDataFile(), StubIncrementalCompilationComponents()) {
createIncrementalDataFile(), SourceRetentionAnnotationHandlerImpl()) {
override fun loadAnnotationProcessors() = processors
private companion object {
@@ -55,14 +55,6 @@ class AnnotationProcessingExtensionForTests(
deleteOnExit()
}
}
private class StubIncrementalCompilationComponents : IncrementalCompilationComponents {
private val sourceRetentionAnnotationHandler = SourceRetentionAnnotationHandlerImpl()
override fun getIncrementalCache(target: TargetId) = null!!
override fun getLookupTracker() = null!!
override fun getSourceRetentionAnnotationHandler() = sourceRetentionAnnotationHandler
}
}
abstract class AbstractProcessorTest : AbstractBytecodeTextTest() {
@@ -233,10 +233,8 @@ class ProcessorTests : AbstractProcessorTest() {
fun testSourceRetention() {
test("SourceRetention", "*") { set, roundEnv, env -> }
val ext = getKapt2Extension()
val incrementalCompilationComponents = ext.incrementalCompilationComponents
assertNotNull(incrementalCompilationComponents)
val annotationHandler = incrementalCompilationComponents!!.getSourceRetentionAnnotationHandler()
val annotations = (annotationHandler as SourceRetentionAnnotationHandlerImpl).sourceRetentionAnnotations.sorted()
val annotationHandler = ext.sourceRetentionAnnotationHandler as SourceRetentionAnnotationHandlerImpl
val annotations = annotationHandler.sourceRetentionAnnotations.sorted()
assertEquals("Source1, Source2, Source3, Source4, Test5\$Source5", annotations.joinToString())
}