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:
+1
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-2
@@ -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");
|
||||
|
||||
|
||||
-1
@@ -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(),
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+3
-3
@@ -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?
|
||||
|
||||
+52
@@ -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
|
||||
}
|
||||
}
|
||||
+18
-3
@@ -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()
|
||||
|
||||
+61
@@ -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()
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -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?
|
||||
|
||||
+9
-4
@@ -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)
|
||||
|
||||
|
||||
+8
-5
@@ -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(
|
||||
|
||||
+16
-4
@@ -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 {
|
||||
|
||||
+108
@@ -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)
|
||||
}
|
||||
}
|
||||
+9
@@ -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'")
|
||||
|
||||
+19
-11
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+28
@@ -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" }
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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())
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+1
-9
@@ -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() {
|
||||
|
||||
+2
-4
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user