From d2193f3873b996da66f3387ea594d26faf1710d1 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Mon, 17 Jan 2022 19:06:04 +0000 Subject: [PATCH] KT-45777: Allow 2 levels of granularity when tracking changes 1. CLASS_LEVEL: allows tracking whether a .class file has changed without tracking what specific parts of the .class file (e.g., fields or methods) have changed. 2. CLASS_MEMBER_LEVEL: allows tracking not only whether a .class file has changed but also what specific parts of the .class file (e.g., fields or methods) have changed. The idea is that for better performance we will use CLASS_LEVEL for classpath entries that are usually unchanged, and CLASS_MEMBER_LEVEL for classpath entries that are frequently changed. We'll work out the specifics in a following commit after some measurements. Support running kotlinc on Windows in ClasspathSnapshotTestCommon Also add tests for different Kotlin class kinds. Add unit tests for CLASS_LEVEL snapshotting and diffing Test: Updated ClasspathSnapshotterTest + ClasspathChangesComputerTest Add ClasspathChangesComputerTest.testMixedClassSnapshotGranularities --- .idea/dictionaries/hungnv.xml | 2 + .../jetbrains/kotlin/incremental/fileUtils.kt | 26 +-- .../incremental/storage/externalizers.kt | 26 ++- .../incremental/IncrementalCompilerRunner.kt | 2 +- .../IncrementalJvmCompilerRunner.kt | 16 +- .../incremental/classpathDiff/ChangeSet.kt | 6 + .../classpathDiff/ClasspathChangesComputer.kt | 139 +++++++----- .../classpathDiff/ClasspathSnapshot.kt | 160 ++++++++------ .../ClasspathSnapshotSerializer.kt | 207 +++++++++++------- .../ClasspathSnapshotShrinker.kt | 126 ++++++----- .../classpathDiff/ClasspathSnapshotter.kt | 40 +++- .../classpathDiff/JavaClassChangesComputer.kt | 34 ++- .../classpathDiff/JavaClassSnapshotter.kt | 27 ++- .../classpathDiff/ProgramSymbol.kt | 65 ++++-- .../ClasspathChangesComputerTest.kt | 157 +++++++++++-- .../ClasspathSnapshotSerializerTest.kt | 6 +- .../ClasspathSnapshotTestCommon.kt | 71 +++++- .../classpathDiff/ClasspathSnapshotterTest.kt | 193 ++++++++++------ .../example/SomeClass$CompanionObject.class | Bin 965 -> 965 bytes .../0/com/example/SomeClass.class | Bin 1691 -> 1695 bytes .../example/SomeClass$CompanionObject.class | Bin 955 -> 955 bytes .../0/com/example/SomeClass.class | Bin 1655 -> 1659 bytes .../0/com/example/SomeClass.kt | 1 + .../0/com/example/SomeClass.kt | 1 + ...Build_CoarseGrainedSecondBuild_Class.class | Bin 0 -> 859 bytes ...stBuild_FineGrainedSecondBuild_Class.class | Bin 0 -> 853 bytes ...Build_CoarseGrainedSecondBuild_Class.class | Bin 0 -> 853 bytes ...stBuild_FineGrainedSecondBuild_Class.class | Bin 0 -> 847 bytes ...Build_CoarseGrainedSecondBuild_Class.class | Bin 0 -> 859 bytes ...stBuild_FineGrainedSecondBuild_Class.class | Bin 0 -> 853 bytes ...Build_CoarseGrainedSecondBuild_Class.class | Bin 0 -> 853 bytes ...stBuild_FineGrainedSecondBuild_Class.class | Bin 0 -> 847 bytes ...tBuild_CoarseGrainedSecondBuild_Class.java | 10 + ...rstBuild_FineGrainedSecondBuild_Class.java | 10 + ...tBuild_CoarseGrainedSecondBuild_Class.java | 10 + ...rstBuild_FineGrainedSecondBuild_Class.java | 10 + ...tBuild_CoarseGrainedSecondBuild_Class.java | 10 + ...rstBuild_FineGrainedSecondBuild_Class.java | 10 + ...tBuild_CoarseGrainedSecondBuild_Class.java | 10 + ...rstBuild_FineGrainedSecondBuild_Class.java | 10 + ...rstBuild_CoarseGrainedSecondBuild_Class.kt | 6 + ...FirstBuild_FineGrainedSecondBuild_Class.kt | 6 + ...rstBuild_CoarseGrainedSecondBuild_Class.kt | 6 + ...FirstBuild_FineGrainedSecondBuild_Class.kt | 6 + ...rstBuild_CoarseGrainedSecondBuild_Class.kt | 6 + ...FirstBuild_FineGrainedSecondBuild_Class.kt | 6 + ...rstBuild_CoarseGrainedSecondBuild_Class.kt | 6 + ...FirstBuild_FineGrainedSecondBuild_Class.kt | 6 + .../ModifiedClassUnchangedMembers.class | Bin 828 -> 859 bytes .../example/ModifiedClassUnchangedMembers.kt | 2 +- .../abi-change/com/example/SimpleClass.class | Bin 861 -> 0 bytes .../com/example/SimpleClass.class | Bin 901 -> 0 bytes .../abi-change/com/example/SimpleClass.class | Bin 861 -> 0 bytes .../kotlin/com/example/SimpleClass.class | Bin 861 -> 0 bytes .../java/com/example/SimpleClass.json | 52 ----- .../com/example/SimpleClass.json | 21 ++ .../com/example/SimpleClass.json | 47 ++++ .../src/com/example/SimpleClass.java | 16 ++ .../com/com/example/FileFacadeKt.class | Bin 0 -> 802 bytes .../com/com/example/MultifileClass.class | Bin 0 -> 809 bytes .../MultifileClass__MultifileClass1Kt.class | Bin 0 -> 892 bytes .../MultifileClass__MultifileClass2Kt.class | Bin 0 -> 892 bytes .../src/com/example/FileFacade.kt | 4 + .../src/com/example/MultifileClass1.kt | 7 + .../src/com/example/MultifileClass2.kt | 7 + .../classes/com/example/SimpleClass.class | Bin 0 -> 1159 bytes .../com/example/SimpleClass.json | 21 ++ .../com/example/SimpleClass.json | 26 ++- .../src/com/example/SimpleClass.kt | 12 + .../abi-change/com/example/SimpleClass.java | 16 -- .../com/example/SimpleClass.java | 16 -- .../abi-change/com/example/SimpleClass.kt | 12 - .../non-abi-change/com/example/SimpleClass.kt | 12 - .../src/java/com/example/SimpleClass.java | 16 -- .../src/kotlin/com/example/SimpleClass.kt | 12 - .../kotlin/gradle/tasks/tasksUtils.kt | 4 +- 76 files changed, 1170 insertions(+), 566 deletions(-) create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/current-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/current-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/current-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/current-classpath/0/com/example/FineGrainedFirstBuild_FineGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/FineGrainedFirstBuild_FineGrainedSecondBuild_Class.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/current-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/current-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/current-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/current-classpath/0/com/example/FineGrainedFirstBuild_FineGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/previous-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/previous-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/previous-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/java/previous-classpath/0/com/example/FineGrainedFirstBuild_FineGrainedSecondBuild_Class.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/current-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/current-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/current-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/current-classpath/0/com/example/FineGrainedFirstBuild_FineGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/previous-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/src/kotlin/previous-classpath/0/com/example/FineGrainedFirstBuild_FineGrainedSecondBuild_Class.kt delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotlin/abi-change/com/example/SimpleClass.class delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotlin/non-abi-change/com/example/SimpleClass.class delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotllin/abi-change/com/example/SimpleClass.class delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes/kotlin/com/example/SimpleClass.class delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/expected-snapshot/java/com/example/SimpleClass.json create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/src/com/example/SimpleClass.java create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/FileFacadeKt.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass__MultifileClass1Kt.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass__MultifileClass2Kt.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/FileFacade.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass1.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass2.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/classes/com/example/SimpleClass.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json rename compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/{expected-snapshot/kotlin => kotlin/testSimpleClass/expected-snapshot}/com/example/SimpleClass.json (59%) create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/src/com/example/SimpleClass.kt delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/src-changed/java/abi-change/com/example/SimpleClass.java delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/src-changed/java/non-abi-change/com/example/SimpleClass.java delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/src-changed/kotlin/abi-change/com/example/SimpleClass.kt delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/src-changed/kotlin/non-abi-change/com/example/SimpleClass.kt delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/src/java/com/example/SimpleClass.java delete mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/src/kotlin/com/example/SimpleClass.kt diff --git a/.idea/dictionaries/hungnv.xml b/.idea/dictionaries/hungnv.xml index eae20ed79ec..de66416ff33 100644 --- a/.idea/dictionaries/hungnv.xml +++ b/.idea/dictionaries/hungnv.xml @@ -1,6 +1,8 @@ + externalizers + granularities multifile shrinker snapshotter diff --git a/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt b/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt index de20d5ebc55..0e2b388f121 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/fileUtils.kt @@ -35,33 +35,33 @@ fun File.isClassFile(): Boolean = */ fun File.cleanDirectoryContents() { when { - isDirectory -> listFiles()!!.forEach { it.forceDeleteRecursively() } + isDirectory -> listFiles()!!.forEach { it.deleteRecursivelyOrThrow() } isFile -> error("File.cleanDirectoryContents does not accept a regular file: $path") - else -> forceMkdirs() + else -> mkdirsOrThrow() } } -/** Deletes this file or directory recursively (if it exists). */ -fun File.forceDeleteRecursively() { +/** Deletes this file or directory recursively (if it exists), throwing an exception if the deletion failed. */ +fun File.deleteRecursivelyOrThrow() { if (!deleteRecursively()) { throw IOException("Could not delete '$path'") } } /** - * Creates this directory (if it does not yet exist). - * - * If this is a regular file, this method will throw an exception. + * Creates this directory (if it does not yet exist), throwing an exception if the directiory creation failed or if a regular file already + * exists at this path. */ @Suppress("SpellCheckingInspection") -fun File.forceMkdirs() { +fun File.mkdirsOrThrow() { when { - this.isDirectory -> { /* Do nothing */ } - this.isFile -> error("File.forceMkdirs does not accept a regular file: $path") + isDirectory -> Unit + isFile -> error("A regular file already exists at this path: $path") else -> { - // Note that if the directory already exists, mkdirs() will return `false`, but here we ensure that the directory does not exist - // before calling mkdirs(), so it's safe to check the returned result of mkdirs() below. - if (!mkdirs()) { + // Note that `mkdirs()` returns `false` if the directory already exists (even though we have checked that the directory did not + // exist earlier, it might just have been created by some other thread). Therefore, we need to check if the directory exists + // again when `mkdirs()` returns `false`. + if (!mkdirs() && !isDirectory) { throw IOException("Could not create directory '$path'") } } diff --git a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt index b2130bfed04..6d2ccfac59f 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt @@ -346,7 +346,7 @@ object ByteArrayExternalizer : DataExternalizer { } } -open class GenericCollectionExternalizer>( +abstract class GenericCollectionExternalizer>( private val elementExternalizer: DataExternalizer, private val newCollection: (size: Int) -> MutableCollection ) : DataExternalizer { @@ -364,6 +364,9 @@ open class GenericCollectionExternalizer>( repeat(size) { collection.add(elementExternalizer.read(input)) } + // We want `collection` to be both a mutable collection (so we can add elements to it as done above) and a type that can be safely + // converted to type `C` (to be used as the returned value of this method). However, there is no type-safe way to express that, so + // we have to use this unsafe cast. @Suppress("UNCHECKED_CAST") return collection as C } @@ -375,12 +378,13 @@ class ListExternalizer(elementExternalizer: DataExternalizer) : class SetExternalizer(elementExternalizer: DataExternalizer) : GenericCollectionExternalizer>(elementExternalizer, { size -> LinkedHashSet(size) }) -class LinkedHashMapExternalizer( +open class MapExternalizer>( private val keyExternalizer: DataExternalizer, - private val valueExternalizer: DataExternalizer -) : DataExternalizer> { + private val valueExternalizer: DataExternalizer, + private val newMap: (size: Int) -> MutableMap = { size -> LinkedHashMap(size) } +) : DataExternalizer { - override fun save(output: DataOutput, map: LinkedHashMap) { + override fun save(output: DataOutput, map: M) { output.writeInt(map.size) for ((key, value) in map) { keyExternalizer.save(output, key) @@ -388,14 +392,20 @@ class LinkedHashMapExternalizer( } } - override fun read(input: DataInput): LinkedHashMap { + override fun read(input: DataInput): M { val size = input.readInt() - val map = LinkedHashMap(size) + val map = newMap(size) repeat(size) { val key = keyExternalizer.read(input) val value = valueExternalizer.read(input) map[key] = value } - return map + @Suppress("UNCHECKED_CAST") + return map as M } } + +class LinkedHashMapExternalizer( + keyExternalizer: DataExternalizer, + valueExternalizer: DataExternalizer +) : MapExternalizer>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) }) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index d641b8a7111..f729ad28d6d 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -216,7 +216,7 @@ abstract class IncrementalCompilerRunner< } it.isFile -> { reporter.reportVerbose { " Deleting file '${it.path}'" } - it.forceDeleteRecursively() + it.deleteRecursivelyOrThrow() } } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index 96c30b2c2ed..b300724c62b 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -52,6 +52,8 @@ import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnable import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailableForJSCompiler import org.jetbrains.kotlin.incremental.classpathDiff.* +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathChangesComputer.computeChangedAndImpactedSet +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrinkClasspath import org.jetbrains.kotlin.incremental.components.ExpectActualTracker import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory @@ -219,8 +221,8 @@ class IncrementalJvmCompilerRunner( // Used by `calculateSourcesToCompileImpl` and `performWorkAfterSuccessfulCompilation` methods below. // Thread safety: There is no concurrent access to these variables. - private var currentClasspathSnapshot: List? = null - private var shrunkCurrentClasspathAgainstPreviousLookups: List? = null + private var currentClasspathSnapshot: List? = null + private var shrunkCurrentClasspathAgainstPreviousLookups: List? = null private fun calculateSourcesToCompileImpl( caches: IncrementalJvmCachesManager, @@ -245,9 +247,15 @@ class IncrementalJvmCompilerRunner( } check(shrunkCurrentClasspathAgainstPreviousLookups == null) shrunkCurrentClasspathAgainstPreviousLookups = reporter.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT) { - ClasspathSnapshotShrinker.shrink(currentClasspathSnapshot!!, caches.lookupCache, reporter) + shrinkClasspath( + currentClasspathSnapshot!!, caches.lookupCache, + ClasspathSnapshotShrinker.MetricsReporter( + reporter, + BuildTime.GET_LOOKUP_SYMBOLS, BuildTime.FIND_REFERENCED_CLASSES, BuildTime.FIND_TRANSITIVELY_REFERENCED_CLASSES + ) + ) } - ClasspathChangesComputer.computeChangedAndImpactedSet( + computeChangedAndImpactedSet( shrunkCurrentClasspathAgainstPreviousLookups!!, classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile, reporter diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ChangeSet.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ChangeSet.kt index 861865b0832..1b57037b7b3 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ChangeSet.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ChangeSet.kt @@ -49,6 +49,12 @@ class ChangeSet( fun addChangedClassMember(className: ClassId, memberName: String) = addChangedClassMembers(className, listOf(memberName)) + fun addChangedTopLevelMembers(packageMemberSet: PackageMemberSet) { + packageMemberSet.packageToMembersMap.forEach { (packageName, memberNames) -> + changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(memberNames) + } + } + fun addChangedTopLevelMembers(packageName: FqName, topLevelMembers: Collection) { if (topLevelMembers.isNotEmpty()) { changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(topLevelMembers) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt index e7096acfc1b..0dedebb8109 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt @@ -30,12 +30,12 @@ object ClasspathChangesComputer { * NOTE: The original classpath may contain duplicate classes, but the shrunk classpath must not contain duplicate classes. */ fun computeChangedAndImpactedSet( - shrunkCurrentClasspathSnapshot: List, + shrunkCurrentClasspathSnapshot: List, shrunkPreviousClasspathSnapshotFile: File, metrics: BuildMetricsReporter ): ChangeSet { val shrunkPreviousClasspathSnapshot = metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT) { - ListExternalizer(ClassSnapshotWithHashExternalizer).loadFromFile(shrunkPreviousClasspathSnapshotFile) + ListExternalizer(AccessibleClassSnapshotExternalizer).loadFromFile(shrunkPreviousClasspathSnapshotFile) } return metrics.measure(BuildTime.COMPUTE_CHANGED_AND_IMPACTED_SET) { computeChangedAndImpactedSet(shrunkCurrentClasspathSnapshot, shrunkPreviousClasspathSnapshot, metrics) @@ -48,22 +48,26 @@ object ClasspathChangesComputer { * NOTE: Each list of classes must not contain duplicates. */ fun computeChangedAndImpactedSet( - currentClassSnapshots: List, - previousClassSnapshots: List, + currentClassSnapshots: List, + previousClassSnapshots: List, metrics: BuildMetricsReporter ): ChangeSet { - val currentClasses: Map = currentClassSnapshots.associateBy { it.classSnapshot.getClassId() } - val previousClasses: Map = previousClassSnapshots.associateBy { it.classSnapshot.getClassId() } + val currentClasses: Map = currentClassSnapshots.associateBy { it.classId } + val previousClasses: Map = previousClassSnapshots.associateBy { it.classId } - val changedCurrentClasses: List = currentClasses.filter { (classId, currentClass) -> + val changedCurrentClasses: List = currentClasses.mapNotNull { (classId, currentClass) -> val previousClass = previousClasses[classId] - previousClass == null || currentClass.hash != previousClass.hash - }.map { it.value.classSnapshot } + if (previousClass == null || currentClass.classAbiHash != previousClass.classAbiHash) { + currentClass + } else null + } - val changedPreviousClasses: List = previousClasses.filter { (classId, previousClass) -> + val changedPreviousClasses: List = previousClasses.mapNotNull { (classId, previousClass) -> val currentClass = currentClasses[classId] - currentClass == null || currentClass.hash != previousClass.hash - }.map { it.value.classSnapshot } + if (currentClass == null || currentClass.classAbiHash != previousClass.classAbiHash) { + previousClass + } else null + } val classChanges = metrics.measure(BuildTime.COMPUTE_CLASS_CHANGES) { computeClassChanges(changedCurrentClasses, changedPreviousClasses, metrics) @@ -74,7 +78,7 @@ object ClasspathChangesComputer { } return metrics.measure(BuildTime.COMPUTE_IMPACTED_SET) { - computeImpactedSet(classChanges, previousClasses.map { it.value.classSnapshot }) + computeImpactedSet(classChanges, previousClassSnapshots) } } @@ -84,20 +88,13 @@ object ClasspathChangesComputer { * * NOTE: Each list of classes must not contain duplicates. */ - fun computeClassChanges( - currentClassSnapshots: List, - previousClassSnapshots: List, + private fun computeClassChanges( + currentClassSnapshots: List, + previousClassSnapshots: List, metrics: BuildMetricsReporter ): ChangeSet { - val isKotlinClass: (ClassSnapshot) -> Boolean = { - when (it) { - is KotlinClassSnapshot -> true - is JavaClassSnapshot -> false - is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}") - } - } - val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition(isKotlinClass) - val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition(isKotlinClass) + val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition { it is KotlinClassSnapshot } + val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition { it is KotlinClassSnapshot } @Suppress("UNCHECKED_CAST") val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) { @@ -121,6 +118,36 @@ object ClasspathChangesComputer { private fun computeKotlinClassChanges( currentClassSnapshots: List, previousClassSnapshots: List + ): ChangeSet { + val (coarseGrainedCurrentClassSnapshots, fineGrainedCurrentClassSnapshots) = + currentClassSnapshots.partition { it.classMemberLevelSnapshot == null } + val (coarseGrainedPreviousClassSnapshots, fineGrainedPreviousClassSnapshots) = + previousClassSnapshots.partition { it.classMemberLevelSnapshot == null } + + return computeCoarseGrainedKotlinClassChanges(coarseGrainedCurrentClassSnapshots, coarseGrainedPreviousClassSnapshots) + + computeFineGrainedKotlinClassChanges(fineGrainedCurrentClassSnapshots, fineGrainedPreviousClassSnapshots) + } + + private fun computeCoarseGrainedKotlinClassChanges( + currentClassSnapshots: List, + previousClassSnapshots: List + ): ChangeSet { + // Note: We have removed unchanged classes earlier in computeChangedAndImpactedSet method, so here we only have changed classes. + return ChangeSet.Collector().run { + (currentClassSnapshots + previousClassSnapshots).forEach { + when (it) { + is RegularKotlinClassSnapshot -> addChangedClass(it.classId) + is PackageFacadeKotlinClassSnapshot -> addChangedTopLevelMembers(it.packageMembers) + is MultifileClassKotlinClassSnapshot -> addChangedTopLevelMembers(it.constants) + } + } + getChanges() + } + } + + private fun computeFineGrainedKotlinClassChanges( + currentClassSnapshots: List, + previousClassSnapshots: List ): ChangeSet { val workingDir = FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true) @@ -135,11 +162,11 @@ object ClasspathChangesComputer { val unusedChangesCollector = ChangesCollector() previousClassSnapshots.forEach { incrementalJvmCache.saveClassToCache( - kotlinClassInfo = it.classInfo, + kotlinClassInfo = it.classMemberLevelSnapshot!!, sourceFiles = null, changesCollector = unusedChangesCollector ) - incrementalJvmCache.markDirty(it.classInfo.className) + incrementalJvmCache.markDirty(it.classMemberLevelSnapshot!!.className) } // Step 2: @@ -152,7 +179,7 @@ object ClasspathChangesComputer { val changesCollector = ChangesCollector() currentClassSnapshots.forEach { incrementalJvmCache.saveClassToCache( - kotlinClassInfo = it.classInfo, + kotlinClassInfo = it.classMemberLevelSnapshot!!, sourceFiles = null, changesCollector = changesCollector ) @@ -172,7 +199,10 @@ object ClasspathChangesComputer { return dirtyData.normalize(currentClassSnapshots, previousClassSnapshots) } - private fun DirtyData.normalize(currentClassSnapshots: List, previousClassSnapshots: List): ChangeSet { + private fun DirtyData.normalize( + currentClassSnapshots: List, + previousClassSnapshots: List + ): ChangeSet { val changedLookupSymbols = dirtyLookupSymbols.filterLookupSymbols(currentClassSnapshots).toSet() + dirtyLookupSymbols.filterLookupSymbols(previousClassSnapshots) @@ -216,7 +246,7 @@ internal object ImpactAnalysis { * * The returned set is also a [ChangeSet], which includes the given changes plus the impacted ones. */ - fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List): ChangeSet { + fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List): ChangeSet { val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots) val impactedClassesResolver = { classId: ClassId -> classIdToSubclasses[classId] ?: emptySet() } @@ -234,14 +264,14 @@ internal object ImpactAnalysis { } } - private fun getClassIdToSubclassesMap(classSnapshots: List): Map> { - val classIds: Set = classSnapshots.map { it.getClassId() }.toSet() // Use Set for presence check + private fun getClassIdToSubclassesMap(classSnapshots: List): Map> { + val classIds: Set = classSnapshots.map { it.classId }.toSet() // Use Set for presence check val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) } val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] } val classIdToSubclasses = mutableMapOf>() classSnapshots.forEach { classSnapshot -> - val classId = classSnapshot.getClassId() + val classId = classSnapshot.classId classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype -> // No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object") if (supertype in classIds) { @@ -257,39 +287,36 @@ internal object ImpactAnalysis { * impacted classes. */ fun findImpactedClassesInclusive(classIds: Set, impactedClassesResolver: (ClassId) -> Set): Set { - val visitedClasses = mutableSetOf() - val toVisitClasses = classIds.toMutableSet() - while (toVisitClasses.isNotEmpty()) { - val nextToVisit = mutableSetOf() - toVisitClasses.forEach { - nextToVisit.addAll(impactedClassesResolver.invoke(it)) - } - visitedClasses.addAll(toVisitClasses) - toVisitClasses.clear() - toVisitClasses.addAll(nextToVisit - visitedClasses) - } - return visitedClasses - } -} + // Standard Breadth-First Search + val visitedAndToVisitClasses = classIds.toMutableSet() + val classesToVisit = ArrayDeque(classIds) -internal fun ClassSnapshot.getClassId(): ClassId { - return when (this) { - is KotlinClassSnapshot -> classInfo.classId - is JavaClassSnapshot -> classId - is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}") + while (classesToVisit.isNotEmpty()) { + val classToVisit = classesToVisit.removeFirst() + val nextClassesToVisit = impactedClassesResolver.invoke(classToVisit) - visitedAndToVisitClasses + visitedAndToVisitClasses.addAll(nextClassesToVisit) + classesToVisit.addAll(nextClassesToVisit) + } + return visitedAndToVisitClasses } } /** - * Returns the [ClassId]s of the supertypes of this class. + * Returns the [ClassId]s of the supertypes of this class (could be empty in some cases). * * @param classIdResolver Resolves the [ClassId] from the [JvmClassName] of a supertype. It may return null if the supertype is outside the * considered set of classes (e.g., "java/lang/Object"). Those supertypes do not need to be included in the returned result. */ -internal fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List { +internal fun AccessibleClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List { return when (this) { - is KotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) } + is RegularKotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) } + is PackageFacadeKotlinClassSnapshot, is MultifileClassKotlinClassSnapshot -> { + // These classes may have supertypes (e.g., kotlin/collections/ArraysKt (MULTIFILE_CLASS) extends + // kotlin/collections/ArraysKt___ArraysKt (MULTIFILE_CLASS_PART)), but we don't have to use that info during impact analysis + // because those inheritors and supertypes should have the same package names, and in package facades only the package names and + // member names matter. + emptyList() + } is JavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) } - is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}") } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshot.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshot.kt index 0af2bb75375..98478fa7088 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshot.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshot.kt @@ -6,8 +6,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff import org.jetbrains.kotlin.incremental.KotlinClassInfo -import org.jetbrains.kotlin.incremental.md5 -import org.jetbrains.kotlin.incremental.storage.toByteArray +import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.resolve.jvm.JvmClassName @@ -26,7 +26,7 @@ class ClasspathEntrySnapshot( * Maps (Unix-style) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory * or jar). */ - val classSnapshots: LinkedHashMap + val classSnapshots: LinkedHashMap ) /** @@ -37,70 +37,83 @@ class ClasspathEntrySnapshot( * `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures, * and should not contain private method signatures, or method implementations. */ -sealed class ClassSnapshot { +sealed class ClassSnapshot - /** Computes the hash of this [ClassSnapshot] and returns a [ClassSnapshotWithHash]. */ - val withHash: ClassSnapshotWithHash by lazy { - ClassSnapshotWithHash(this, ClassSnapshotExternalizer.toByteArray(this).md5()) - } -} +/** [ClassSnapshot] of an accessible class. See [InaccessibleClassSnapshot] for info on the accessibility of a class. */ +sealed class AccessibleClassSnapshot : ClassSnapshot() { + abstract val classId: ClassId -/** Contains a [ClassSnapshot] and its hash. */ -class ClassSnapshotWithHash(val classSnapshot: ClassSnapshot, val hash: Long) { - - override fun toString() = classSnapshot.toString() -} - -/** [ClassSnapshot] of a Kotlin class. */ -class KotlinClassSnapshot( - val classInfo: KotlinClassInfo, - val supertypes: List, - - /** - * Package-level members if this class is a package facade (classInfo.classKind != KotlinClassHeader.Kind.CLASS). - * - * Note that MULTIFILE_CLASS classes do not have proto data, so we can't extract their package-level members even though they are - * package facades. - * - * Therefore, the [packageMembers] property below is not null iff classInfo.classKind !in setOf(CLASS, MULTIFILE_CLASS) - */ - val packageMembers: List? -) : ClassSnapshot() { - - override fun toString() = classInfo.classId.toString() -} - -/** [ClassSnapshot] of a Java class. */ -class JavaClassSnapshot( - - /** [ClassId] of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */ - val classId: ClassId, - - /** The superclass and interfaces of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */ - val supertypes: List, - - /** [AbiSnapshot] of the class excluding its fields and methods. */ - val classAbiExcludingMembers: AbiSnapshot, - - /** [AbiSnapshot]s of the class's fields. */ - val fieldsAbi: List, - - /** [AbiSnapshot]s of the class's methods. */ - val methodsAbi: List - -) : ClassSnapshot() { - - val className by lazy { - JvmClassName.byClassId(classId).also { - check(it == JvmClassName.byInternalName(classAbiExcludingMembers.name)) - } - } + /** The hash of the class's ABI. */ + abstract val classAbiHash: Long override fun toString() = classId.toString() } -/** The ABI snapshot of a Java element (e.g., class, field, or method). */ -open class AbiSnapshot( +/** [ClassSnapshot] of a Kotlin class. */ +sealed class KotlinClassSnapshot : AccessibleClassSnapshot() { + + /** Snapshot of this class when [ClassSnapshotGranularity] == [CLASS_MEMBER_LEVEL], null otherwise. */ + abstract val classMemberLevelSnapshot: KotlinClassInfo? +} + +/** [KotlinClassSnapshot] where class kind == [CLASS]. */ +class RegularKotlinClassSnapshot( + override val classId: ClassId, + override val classAbiHash: Long, + override val classMemberLevelSnapshot: KotlinClassInfo?, + val supertypes: List +) : KotlinClassSnapshot() + +/** [KotlinClassSnapshot] where class kind == [FILE_FACADE] or [MULTIFILE_CLASS_PART]. */ +class PackageFacadeKotlinClassSnapshot( + override val classId: ClassId, + override val classAbiHash: Long, + override val classMemberLevelSnapshot: KotlinClassInfo?, + val packageMembers: PackageMemberSet +) : KotlinClassSnapshot() + +/** + * [KotlinClassSnapshot] where class kind == [MULTIFILE_CLASS]. + * + * NOTE: We have to handle [MULTIFILE_CLASS] differently from [FILE_FACADE] and [MULTIFILE_CLASS_PART] because [MULTIFILE_CLASS] classes + * don't contain proto data. Except for constants (see below), it is actually okay to ignore [MULTIFILE_CLASS] because any change in a + * [MULTIFILE_CLASS] will have an associated change in one of its [MULTIFILE_CLASS_PART]s, so the change will be detected when we analyze + * the [MULTIFILE_CLASS_PART]s. + * + * However, if there is a constant is defined in a [MULTIFILE_CLASS], that constant will have a declared value in the [MULTIFILE_CLASS] but + * not in its [MULTIFILE_CLASS_PART]s. Therefore, we'll need to track constants for [MULTIFILE_CLASS]. (We don't have to do this for inline + * functions or other package members as those are defined in [MULTIFILE_CLASS_PART]s.) + */ +class MultifileClassKotlinClassSnapshot( + override val classId: ClassId, + override val classAbiHash: Long, + override val classMemberLevelSnapshot: KotlinClassInfo?, + val constants: PackageMemberSet +) : KotlinClassSnapshot() + +/** [ClassSnapshot] of a Java class. */ +class JavaClassSnapshot( + override val classId: ClassId, + override val classAbiHash: Long, + /** Snapshot of this class when [ClassSnapshotGranularity] == [CLASS_MEMBER_LEVEL], null otherwise. */ + val classMemberLevelSnapshot: JavaClassMemberLevelSnapshot?, + val supertypes: List +) : AccessibleClassSnapshot() + +/** Snapshot of a Java class when [ClassSnapshotGranularity] == [CLASS_MEMBER_LEVEL]. */ +class JavaClassMemberLevelSnapshot( + /** [JavaElementSnapshot] of the class excluding its fields and methods. */ + val classAbiExcludingMembers: JavaElementSnapshot, + + /** [JavaElementSnapshot]s of the class's fields. */ + val fieldsAbi: List, + + /** [JavaElementSnapshot]s of the class's methods. */ + val methodsAbi: List +) + +/** Snapshot of a Java class or a Java class member (field or method). */ +open class JavaElementSnapshot( /** The name of the Java element. It is part of the Java element's ABI. */ val name: String, @@ -109,15 +122,16 @@ open class AbiSnapshot( val abiHash: Long ) -/** TEST-ONLY: An [AbiSnapshot] that is used for testing only and must not be used in production code. */ -class AbiSnapshotForTests( +/** TEST-ONLY: A [JavaElementSnapshot] that is used for testing only and must not be used in production code. */ +class JavaElementSnapshotForTests( name: String, abiHash: Long, /** The Java element's ABI, captured in a [String]. */ - @Suppress("unused") val abiValue: String + @Suppress("unused") // Used by Gson reflection + val abiValue: String -) : AbiSnapshot(name, abiHash) +) : JavaElementSnapshot(name, abiHash) /** * [ClassSnapshot] of an inaccessible class. @@ -126,3 +140,19 @@ class AbiSnapshotForTests( * will not require recompilation of other source files. */ object InaccessibleClassSnapshot : ClassSnapshot() + +/** The granularity of a [ClassSnapshot]. */ +enum class ClassSnapshotGranularity { + + /** + * Snapshotting level that allows tracking whether a .class file has changed without tracking what specific parts of the .class file + * (e.g., fields or methods) have changed. + */ + CLASS_LEVEL, + + /** + * Snapshotting level that allows tracking not only whether a .class file has changed but also what specific parts of the .class file + * (e.g., fields or methods) have changed. + */ + CLASS_MEMBER_LEVEL +} diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt index 0637a4661a2..f7d73f03267 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt @@ -48,76 +48,119 @@ object CachedClasspathSnapshotSerializer { } } +open class DataExternalizerForSealedClass( + val baseClass: Class, + val inheritorClasses: List>, + val inheritorExternalizers: List> +) : DataExternalizer { + + override fun save(output: DataOutput, objectToExternalize: T) { + val inheritorClassIndex = + inheritorClasses.indexOfFirst { it.isAssignableFrom(objectToExternalize!!::class.java) }.also { check(it != -1) } + output.writeInt(inheritorClassIndex) + @Suppress("UNCHECKED_CAST") + (inheritorExternalizers[inheritorClassIndex] as DataExternalizer).save(output, objectToExternalize) + } + + override fun read(input: DataInput): T { + val inheritorClassIndex = input.readInt() + @Suppress("UNCHECKED_CAST") + return inheritorExternalizers[inheritorClassIndex].read(input) as T + } +} + object ClasspathEntrySnapshotExternalizer : DataExternalizer { override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) { - LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).save(output, snapshot.classSnapshots) + LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotExternalizer).save(output, snapshot.classSnapshots) } override fun read(input: DataInput): ClasspathEntrySnapshot { return ClasspathEntrySnapshot( - classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).read(input) + classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotExternalizer).read(input) ) } } -object ClassSnapshotExternalizer : DataExternalizer { +object ClassSnapshotExternalizer : DataExternalizerForSealedClass( + baseClass = ClassSnapshot::class.java, + inheritorClasses = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java), + inheritorExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer) +) - override fun save(output: DataOutput, snapshot: ClassSnapshot) { - when (snapshot) { - is KotlinClassSnapshot -> { - output.writeString(KotlinClassSnapshot::class.java.name) - KotlinClassSnapshotExternalizer.save(output, snapshot) - } - is JavaClassSnapshot -> { - output.writeString(JavaClassSnapshot::class.java.name) - JavaClassSnapshotExternalizer.save(output, snapshot) - } - is InaccessibleClassSnapshot -> { - output.writeString(InaccessibleClassSnapshot::class.java.name) - InaccessibleClassSnapshotExternalizer.save(output, snapshot) - } - } - } +object AccessibleClassSnapshotExternalizer : DataExternalizerForSealedClass( + baseClass = AccessibleClassSnapshot::class.java, + inheritorClasses = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java), + inheritorExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer) +) - override fun read(input: DataInput): ClassSnapshot { - return when (val className = input.readString()) { - KotlinClassSnapshot::class.java.name -> KotlinClassSnapshotExternalizer.read(input) - JavaClassSnapshot::class.java.name -> JavaClassSnapshotExternalizer.read(input) - InaccessibleClassSnapshot::class.java.name -> InaccessibleClassSnapshotExternalizer.read(input) - else -> error("Unrecognized class name: $className") - } - } -} +object KotlinClassSnapshotExternalizer : DataExternalizerForSealedClass( + baseClass = KotlinClassSnapshot::class.java, + inheritorClasses = listOf( + RegularKotlinClassSnapshot::class.java, + PackageFacadeKotlinClassSnapshot::class.java, + MultifileClassKotlinClassSnapshot::class.java + ), + inheritorExternalizers = listOf( + RegularKotlinClassSnapshotExternalizer, + PackageFacadeKotlinClassSnapshotExternalizer, + MultifileClassKotlinClassSnapshotExternalizer + ) +) -object ClassSnapshotWithHashExternalizer : DataExternalizer { +object RegularKotlinClassSnapshotExternalizer : DataExternalizer { - override fun save(output: DataOutput, snapshot: ClassSnapshotWithHash) { - ClassSnapshotExternalizer.save(output, snapshot.classSnapshot) - LongExternalizer.save(output, snapshot.hash) - } - - override fun read(input: DataInput): ClassSnapshotWithHash { - return ClassSnapshotWithHash( - classSnapshot = ClassSnapshotExternalizer.read(input), - hash = LongExternalizer.read(input) - ) - } -} - -object KotlinClassSnapshotExternalizer : DataExternalizer { - - override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) { - KotlinClassInfoExternalizer.save(output, snapshot.classInfo) + override fun save(output: DataOutput, snapshot: RegularKotlinClassSnapshot) { + ClassIdExternalizer.save(output, snapshot.classId) + LongExternalizer.save(output, snapshot.classAbiHash) + NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot) ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes) - NullableValueExternalizer(ListExternalizer(PackageMemberExternalizer)).save(output, snapshot.packageMembers) } - override fun read(input: DataInput): KotlinClassSnapshot { - return KotlinClassSnapshot( - classInfo = KotlinClassInfoExternalizer.read(input), - supertypes = ListExternalizer(JvmClassNameExternalizer).read(input), - packageMembers = NullableValueExternalizer(ListExternalizer(PackageMemberExternalizer)).read(input) + override fun read(input: DataInput): RegularKotlinClassSnapshot { + return RegularKotlinClassSnapshot( + classId = ClassIdExternalizer.read(input), + classAbiHash = LongExternalizer.read(input), + classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input), + supertypes = ListExternalizer(JvmClassNameExternalizer).read(input) + ) + } +} + +object PackageFacadeKotlinClassSnapshotExternalizer : DataExternalizer { + + override fun save(output: DataOutput, snapshot: PackageFacadeKotlinClassSnapshot) { + ClassIdExternalizer.save(output, snapshot.classId) + LongExternalizer.save(output, snapshot.classAbiHash) + NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot) + PackageMemberSetExternalizer.save(output, snapshot.packageMembers) + } + + override fun read(input: DataInput): PackageFacadeKotlinClassSnapshot { + return PackageFacadeKotlinClassSnapshot( + classId = ClassIdExternalizer.read(input), + classAbiHash = LongExternalizer.read(input), + classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input), + packageMembers = PackageMemberSetExternalizer.read(input) + ) + } +} + +object MultifileClassKotlinClassSnapshotExternalizer : DataExternalizer { + + override fun save(output: DataOutput, snapshot: MultifileClassKotlinClassSnapshot) { + ClassIdExternalizer.save(output, snapshot.classId) + LongExternalizer.save(output, snapshot.classAbiHash) + NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot) + PackageMemberSetExternalizer.save(output, snapshot.constants) + } + + override fun read(input: DataInput): MultifileClassKotlinClassSnapshot { + return MultifileClassKotlinClassSnapshot( + classId = ClassIdExternalizer.read(input), + classAbiHash = LongExternalizer.read(input), + classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input), + constants = PackageMemberSetExternalizer.read(input) ) } } @@ -126,7 +169,7 @@ object KotlinClassInfoExternalizer : DataExternalizer { override fun save(output: DataOutput, info: KotlinClassInfo) { ClassIdExternalizer.save(output, info.classId) - output.writeInt(info.classKind.id) + IntExternalizer.save(output, info.classKind.id) ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList()) ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList()) NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName) @@ -137,7 +180,7 @@ object KotlinClassInfoExternalizer : DataExternalizer { override fun read(input: DataInput): KotlinClassInfo { return KotlinClassInfo( classId = ClassIdExternalizer.read(input), - classKind = KotlinClassHeader.Kind.getById(input.readInt()), + classKind = KotlinClassHeader.Kind.getById(IntExternalizer.read(input)), classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(), classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(), multifileClassName = NullableValueExternalizer(StringExternalizer).read(input), @@ -147,17 +190,15 @@ object KotlinClassInfoExternalizer : DataExternalizer { } } -object PackageMemberExternalizer : DataExternalizer { +object PackageMemberSetExternalizer : DataExternalizer { - override fun save(output: DataOutput, packageMember: PackageMember) { - FqNameExternalizer.save(output, packageMember.packageFqName) - StringExternalizer.save(output, packageMember.memberName) + override fun save(output: DataOutput, set: PackageMemberSet) { + MapExternalizer(FqNameExternalizer, SetExternalizer(StringExternalizer)).save(output, set.packageToMembersMap) } - override fun read(input: DataInput): PackageMember { - return PackageMember( - packageFqName = FqNameExternalizer.read(input), - memberName = StringExternalizer.read(input) + override fun read(input: DataInput): PackageMemberSet { + return PackageMemberSet( + packageToMembersMap = MapExternalizer(FqNameExternalizer, SetExternalizer(StringExternalizer)).read(input) ) } } @@ -166,32 +207,50 @@ object JavaClassSnapshotExternalizer : DataExternalizer { override fun save(output: DataOutput, snapshot: JavaClassSnapshot) { ClassIdExternalizer.save(output, snapshot.classId) + LongExternalizer.save(output, snapshot.classAbiHash) + NullableValueExternalizer(JavaClassMemberLevelSnapshotExternalizer).save(output, snapshot.classMemberLevelSnapshot) ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes) - AbiSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers) - ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.fieldsAbi) - ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.methodsAbi) } override fun read(input: DataInput): JavaClassSnapshot { return JavaClassSnapshot( classId = ClassIdExternalizer.read(input), - supertypes = ListExternalizer(JvmClassNameExternalizer).read(input), - classAbiExcludingMembers = AbiSnapshotExternalizer.read(input), - fieldsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input), - methodsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input) + classAbiHash = LongExternalizer.read(input), + classMemberLevelSnapshot = NullableValueExternalizer(JavaClassMemberLevelSnapshotExternalizer).read(input), + supertypes = ListExternalizer(JvmClassNameExternalizer).read(input) ) } } -object AbiSnapshotExternalizer : DataExternalizer { +object JavaClassMemberLevelSnapshotExternalizer : DataExternalizer { - override fun save(output: DataOutput, value: AbiSnapshot) { - output.writeString(value.name) + override fun save(output: DataOutput, snapshot: JavaClassMemberLevelSnapshot) { + JavaElementSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers) + ListExternalizer(JavaElementSnapshotExternalizer).save(output, snapshot.fieldsAbi) + ListExternalizer(JavaElementSnapshotExternalizer).save(output, snapshot.methodsAbi) + } + + override fun read(input: DataInput): JavaClassMemberLevelSnapshot { + return JavaClassMemberLevelSnapshot( + classAbiExcludingMembers = JavaElementSnapshotExternalizer.read(input), + fieldsAbi = ListExternalizer(JavaElementSnapshotExternalizer).read(input), + methodsAbi = ListExternalizer(JavaElementSnapshotExternalizer).read(input) + ) + } +} + +object JavaElementSnapshotExternalizer : DataExternalizer { + + override fun save(output: DataOutput, value: JavaElementSnapshot) { + StringExternalizer.save(output, value.name) LongExternalizer.save(output, value.abiHash) } - override fun read(input: DataInput): AbiSnapshot { - return AbiSnapshot(name = input.readString(), abiHash = LongExternalizer.read(input)) + override fun read(input: DataInput): JavaElementSnapshot { + return JavaElementSnapshot( + name = StringExternalizer.read(input), + abiHash = LongExternalizer.read(input) + ) } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotShrinker.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotShrinker.kt index 8511a454863..031a7050331 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotShrinker.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotShrinker.kt @@ -5,7 +5,10 @@ package org.jetbrains.kotlin.incremental.classpathDiff -import org.jetbrains.kotlin.build.report.metrics.* +import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter +import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric +import org.jetbrains.kotlin.build.report.metrics.BuildTime +import org.jetbrains.kotlin.build.report.metrics.measure import org.jetbrains.kotlin.incremental.ClasspathChanges import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.NoChanges import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.ToBeComputedByIncrementalCompiler @@ -13,12 +16,12 @@ import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnable import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun import org.jetbrains.kotlin.incremental.LookupStorage import org.jetbrains.kotlin.incremental.LookupSymbol -import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrink +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrinkClasses +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrinkClasspath import org.jetbrains.kotlin.incremental.storage.ListExternalizer import org.jetbrains.kotlin.incremental.storage.LookupSymbolKey import org.jetbrains.kotlin.incremental.storage.loadFromFile import org.jetbrains.kotlin.incremental.storage.saveToFile -import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.resolve.jvm.JvmClassName @@ -27,29 +30,29 @@ object ClasspathSnapshotShrinker { /** * Shrinks the given classes by retaining only classes that are referenced by the lookup symbols stored in the given [LookupStorage]. */ - fun shrink( - allClasses: List, + fun shrinkClasspath( + allClasses: List, lookupStorage: LookupStorage, - metrics: BuildMetricsReporter = DoNothingBuildMetricsReporter - ): List { - val lookupSymbols = metrics.measure(BuildTime.GET_LOOKUP_SYMBOLS) { + metrics: MetricsReporter = MetricsReporter() + ): List { + val lookupSymbols = metrics.getLookupSymbols { lookupStorage.lookupSymbols - .map { LookupSymbol(it.name, it.scope) } - .filterLookupSymbols(allClasses.map { it.classSnapshot }) + .map { LookupSymbol(name = it.name, scope = it.scope) } + .filterLookupSymbols(allClasses) } - return shrink(allClasses, lookupSymbols, metrics) + return shrinkClasses(allClasses, lookupSymbols, metrics) } /** Shrinks the given classes by retaining only classes that are referenced by the given lookup symbols. */ - fun shrink( - allClasses: List, + fun shrinkClasses( + allClasses: List, lookupSymbols: List, - metrics: BuildMetricsReporter = DoNothingBuildMetricsReporter - ): List { - val referencedClasses = metrics.measure(BuildTime.FIND_REFERENCED_CLASSES) { + metrics: MetricsReporter = MetricsReporter() + ): List { + val referencedClasses = metrics.findReferencedClasses { findReferencedClasses(allClasses, lookupSymbols) } - return metrics.measure(BuildTime.FIND_TRANSITIVELY_REFERENCED_CLASSES) { + return metrics.findTransitivelyReferencedClasses { findTransitivelyReferencedClasses(allClasses, referencedClasses) } } @@ -57,12 +60,12 @@ object ClasspathSnapshotShrinker { /** * Finds classes that are referenced by the given lookup symbols. * - * Note: It's okay to over-approximate referenced classes. + * Note: It's okay to over-approximate the result. */ private fun findReferencedClasses( - allClasses: List, + allClasses: List, lookupSymbols: List - ): List { + ): List { val lookedUpClassIds: Set = lookupSymbols.mapNotNullTo(mutableSetOf()) { when (it) { is ClassSymbol -> it.classId @@ -70,18 +73,14 @@ object ClasspathSnapshotShrinker { is PackageMember -> null } } - val lookedUpPackageMembers: Set = lookupSymbols.filterIsInstanceTo(mutableSetOf()) + val lookedUpPackageMembers: PackageMemberSet = + lookupSymbols.filterIsInstanceTo>(mutableSetOf()).compact() return allClasses.filter { - val isPackageFacade = - it.classSnapshot is KotlinClassSnapshot && it.classSnapshot.classInfo.classKind != KotlinClassHeader.Kind.CLASS - if (isPackageFacade) { - // If packageMembers == null (e.g., if classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS -- see - // `KotlinClassSnapshot.packageMembers`'s kdoc), it means that we don't have the information, so we will always include the - // class (it's okay to over-approximate the result). - (it.classSnapshot as KotlinClassSnapshot).packageMembers?.any { member -> member in lookedUpPackageMembers } ?: true - } else { - it.classSnapshot.getClassId() in lookedUpClassIds + when (it) { + is RegularKotlinClassSnapshot, is JavaClassSnapshot -> it.classId in lookedUpClassIds + is PackageFacadeKotlinClassSnapshot -> it.packageMembers.containsElementsIn(lookedUpPackageMembers) + is MultifileClassKotlinClassSnapshot -> it.constants.containsElementsIn(lookedUpPackageMembers) } } } @@ -93,10 +92,10 @@ object ClasspathSnapshotShrinker { * The returned list includes the given referenced classes plus the transitively referenced ones. */ private fun findTransitivelyReferencedClasses( - allClasses: List, - referencedClasses: List - ): List { - val classIdToClassSnapshot = allClasses.associateBy { it.classSnapshot.getClassId() } + allClasses: List, + referencedClasses: List + ): List { + val classIdToClassSnapshot = allClasses.associateBy { it.classId } val classIds: Set = classIdToClassSnapshot.keys // Use Set for presence check val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) } val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] } @@ -105,15 +104,30 @@ object ClasspathSnapshotShrinker { // No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object") @Suppress("SimpleRedundantLet") classIdToClassSnapshot[classId]?.let { - it.classSnapshot.getSupertypes(classNameToClassIdResolver).filter { supertype -> supertype in classIds }.toSet() + it.getSupertypes(classNameToClassIdResolver).filterTo(mutableSetOf()) { supertype -> supertype in classIds } } ?: emptySet() } - val referencedClassIds = referencedClasses.map { it.classSnapshot.getClassId() }.toSet() + val referencedClassIds = referencedClasses.mapTo(mutableSetOf()) { it.classId } val transitivelyReferencedClassIds: Set = ImpactAnalysis.findImpactedClassesInclusive(referencedClassIds, supertypesResolver) // Use Set for presence check - return allClasses.filter { it.classSnapshot.getClassId() in transitivelyReferencedClassIds } + return allClasses.filter { it.classId in transitivelyReferencedClassIds } + } + + /** + * Helper class to allow the caller of [ClasspathSnapshotShrinker] to provide a list of [BuildTime]s as different callers may want to + * record different [BuildTime]s (because the [BuildTime.parent]s are different). + */ + class MetricsReporter( + private val metrics: BuildMetricsReporter? = null, + private val getLookupSymbols: BuildTime? = null, + private val findReferencedClasses: BuildTime? = null, + private val findTransitivelyReferencedClasses: BuildTime? = null + ) { + fun getLookupSymbols(fn: () -> T) = metrics?.measure(getLookupSymbols!!, fn) ?: fn() + fun findReferencedClasses(fn: () -> T) = metrics?.measure(findReferencedClasses!!, fn) ?: fn() + fun findTransitivelyReferencedClasses(fn: () -> T) = metrics?.measure(findTransitivelyReferencedClasses!!, fn) ?: fn() } } @@ -136,8 +150,8 @@ object ClasspathSnapshotShrinker { * snapshotting), even though it seems more efficient to do so. For correctness, we need to look at the entire classpath first, remove * duplicate classes, and then remove inaccessible classes. */ -internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List { - return getNonDuplicateClassSnapshots().filter { it.classSnapshot !is InaccessibleClassSnapshot } +internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List { + return getNonDuplicateClassSnapshots().filterIsInstance() } /** @@ -145,8 +159,8 @@ internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List { - val classSnapshots = LinkedHashMap(classpathEntrySnapshots.sumOf { it.classSnapshots.size }) +private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List { + val classSnapshots = LinkedHashMap(classpathEntrySnapshots.sumOf { it.classSnapshots.size }) for (classpathEntrySnapshot in classpathEntrySnapshots) { for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) { classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot) @@ -160,12 +174,12 @@ private sealed class ShrinkMode { object NoChanges : ShrinkMode() class IncrementalNoNewLookups( - val shrunkCurrentClasspathAgainstPreviousLookups: List, + val shrunkCurrentClasspathAgainstPreviousLookups: List, ) : ShrinkMode() class Incremental( - val currentClasspathSnapshot: List, - val shrunkCurrentClasspathAgainstPreviousLookups: List, + val currentClasspathSnapshot: List, + val shrunkCurrentClasspathAgainstPreviousLookups: List, val addedLookupSymbols: Set ) : ShrinkMode() @@ -175,8 +189,8 @@ private sealed class ShrinkMode { internal fun shrinkAndSaveClasspathSnapshot( classpathChanges: ClasspathChanges.ClasspathSnapshotEnabled, lookupStorage: LookupStorage, - currentClasspathSnapshot: List?, // Not null iff classpathChanges is ToBeComputedByIncrementalCompiler - shrunkCurrentClasspathAgainstPreviousLookups: List?, // Same as above + currentClasspathSnapshot: List?, // Not null iff classpathChanges is ToBeComputedByIncrementalCompiler + shrunkCurrentClasspathAgainstPreviousLookups: List?, // Same as above metrics: BuildMetricsReporter ) { // In the following, we'll try to shrink the classpath snapshot incrementally when possible. @@ -191,7 +205,7 @@ internal fun shrinkAndSaveClasspathSnapshot( } else { val shrunkPreviousClasspathAgainstPreviousLookups = metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) { - ListExternalizer(ClassSnapshotWithHashExternalizer) + ListExternalizer(AccessibleClassSnapshotExternalizer) .loadFromFile(classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile) } ShrinkMode.Incremental( @@ -219,7 +233,7 @@ internal fun shrinkAndSaveClasspathSnapshot( } // Shrink current classpath against current lookups - val shrunkCurrentClasspath: List? = when (shrinkMode) { + val shrunkCurrentClasspath: List? = when (shrinkMode) { is ShrinkMode.NoChanges -> null is ShrinkMode.IncrementalNoNewLookups -> { // There are no new lookups, so @@ -227,13 +241,12 @@ internal fun shrinkAndSaveClasspathSnapshot( shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups } is ShrinkMode.Incremental -> metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) { - val shrunkClasses = shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups.map { it.classSnapshot.getClassId() }.toSet() - val notYetShrunkClasses = shrinkMode.currentClasspathSnapshot.filter { it.classSnapshot.getClassId() !in shrunkClasses } + val shrunkClasses = shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups.mapTo(mutableSetOf()) { it.classId } + val notYetShrunkClasses = shrinkMode.currentClasspathSnapshot.filter { it.classId !in shrunkClasses } val addedLookupSymbols = shrinkMode.addedLookupSymbols - .map { LookupSymbol(it.name, it.scope) } - .filterLookupSymbols(shrinkMode.currentClasspathSnapshot.map { it.classSnapshot }) - // Don't provide a BuildMetricsReporter for the following call as the sub-BuildTimes in it have a different parent - val shrunkRemainingClassesAgainstNewLookups = shrink(notYetShrunkClasses, addedLookupSymbols) + .map { LookupSymbol(name = it.name, scope = it.scope) } + .filterLookupSymbols(notYetShrunkClasses) + val shrunkRemainingClassesAgainstNewLookups = shrinkClasses(notYetShrunkClasses, addedLookupSymbols) shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups + shrunkRemainingClassesAgainstNewLookups } @@ -244,8 +257,7 @@ internal fun shrinkAndSaveClasspathSnapshot( .removeDuplicateAndInaccessibleClasses() } metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) { - // Don't provide a BuildMetricsReporter for the following call as the sub-BuildTimes in it have a different parent - shrink(classpathSnapshot, lookupStorage) + shrinkClasspath(classpathSnapshot, lookupStorage) } } } @@ -257,7 +269,7 @@ internal fun shrinkAndSaveClasspathSnapshot( } } else { metrics.measure(BuildTime.SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) { - ListExternalizer(ClassSnapshotWithHashExternalizer).saveToFile( + ListExternalizer(AccessibleClassSnapshotExternalizer).saveToFile( classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile, shrunkCurrentClasspath!! ) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt index c8ef77cda7b..29b4f2ee0ce 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt @@ -7,6 +7,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.incremental.ChangesCollector.Companion.getNonPrivateMemberNames +import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.* +import org.jetbrains.kotlin.incremental.storage.toByteArray import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.* import org.jetbrains.kotlin.name.ClassId import java.io.File @@ -29,7 +31,7 @@ object ClasspathEntrySnapshotter { ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents) } - val snapshots = ClassSnapshotter.snapshot(classes).map { it.withHash } + val snapshots = ClassSnapshotter.snapshot(classes) val relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zip(snapshots).toMap(LinkedHashMap()) return ClasspathEntrySnapshot(relativePathsToSnapshotsMap) @@ -40,7 +42,11 @@ object ClasspathEntrySnapshotter { object ClassSnapshotter { /** Creates [ClassSnapshot]s of the given classes. */ - fun snapshot(classes: List, includeDebugInfoInJavaSnapshot: Boolean? = null): List { + fun snapshot( + classes: List, + granularity: ClassSnapshotGranularity = CLASS_MEMBER_LEVEL, + includeDebugInfoInJavaSnapshot: Boolean = false + ): List { // Find inaccessible classes first val classesInfo: List = classes.map { it.classInfo } val inaccessibleClassesInfo: Set = getInaccessibleClasses(classesInfo).toSet() @@ -48,23 +54,35 @@ object ClassSnapshotter { return classes.map { when { it.classInfo in inaccessibleClassesInfo -> InaccessibleClassSnapshot - it.classInfo.isKotlinClass -> snapshotKotlinClass(it) - else -> JavaClassSnapshotter.snapshot(it, includeDebugInfoInJavaSnapshot) + it.classInfo.isKotlinClass -> snapshotKotlinClass(it, granularity) + else -> JavaClassSnapshotter.snapshot(it, granularity, includeDebugInfoInJavaSnapshot) } } } /** Creates [KotlinClassSnapshot] of the given Kotlin class (the caller must ensure that the given class is a Kotlin class). */ - private fun snapshotKotlinClass(classFile: ClassFileWithContents): KotlinClassSnapshot { + private fun snapshotKotlinClass(classFile: ClassFileWithContents, granularity: ClassSnapshotGranularity): KotlinClassSnapshot { val kotlinClassInfo = KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents) - val packageMembers = when (kotlinClassInfo.classKind) { - CLASS, MULTIFILE_CLASS -> null // See `KotlinClassSnapshot.packageMembers`'s kdoc - else -> (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames().map { - PackageMember(kotlinClassInfo.classId.packageFqName, it) - } + val classId = kotlinClassInfo.classId + val classAbiHash = KotlinClassInfoExternalizer.toByteArray(kotlinClassInfo).md5() + val classMemberLevelSnapshot = kotlinClassInfo.takeIf { granularity == CLASS_MEMBER_LEVEL } + + return when (kotlinClassInfo.classKind) { + CLASS -> RegularKotlinClassSnapshot(classId, classAbiHash, classMemberLevelSnapshot, classFile.classInfo.supertypes) + FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot( + classId, classAbiHash, classMemberLevelSnapshot, + packageMembers = PackageMemberSet( + mapOf(classId.packageFqName to (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames()) + ) + ) + MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot( + classId, classAbiHash, classMemberLevelSnapshot, + constants = PackageMemberSet(mapOf(classId.packageFqName to kotlinClassInfo.constantsMap.keys)) + ) + SYNTHETIC_CLASS -> error("Unexpected class $classId with class kind ${SYNTHETIC_CLASS.name} (synthetic classes should have been removed earlier)") + UNKNOWN -> error("Can't handle class $classId with class kind ${UNKNOWN.name}") } - return KotlinClassSnapshot(kotlinClassInfo, classFile.classInfo.supertypes, packageMembers) } /** diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassChangesComputer.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassChangesComputer.kt index 57a3d1fc7ec..fd76e279be5 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassChangesComputer.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassChangesComputer.kt @@ -54,24 +54,42 @@ object JavaClassChangesComputer { previousClassSnapshot: JavaClassSnapshot, changes: ChangeSet.Collector ) { + if (currentClassSnapshot.classAbiHash == previousClassSnapshot.classAbiHash) return + val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) } - if (currentClassSnapshot.classAbiExcludingMembers.abiHash != previousClassSnapshot.classAbiExcludingMembers.abiHash) { - changes.addChangedClass(classId) + if (currentClassSnapshot.classMemberLevelSnapshot != null && previousClassSnapshot.classMemberLevelSnapshot != null) { + if (currentClassSnapshot.classMemberLevelSnapshot.classAbiExcludingMembers.abiHash + != previousClassSnapshot.classMemberLevelSnapshot.classAbiExcludingMembers.abiHash + ) { + changes.addChangedClass(classId) + } else { + collectClassMemberChanges( + classId, + currentClassSnapshot.classMemberLevelSnapshot.fieldsAbi, + previousClassSnapshot.classMemberLevelSnapshot.fieldsAbi, + changes + ) + collectClassMemberChanges( + classId, + currentClassSnapshot.classMemberLevelSnapshot.methodsAbi, + previousClassSnapshot.classMemberLevelSnapshot.methodsAbi, + changes + ) + } } else { - collectClassMemberChanges(classId, currentClassSnapshot.fieldsAbi, previousClassSnapshot.fieldsAbi, changes) - collectClassMemberChanges(classId, currentClassSnapshot.methodsAbi, previousClassSnapshot.methodsAbi, changes) + changes.addChangedClass(classId) } } /** Collects changes between two lists of fields/methods within a class. */ private fun collectClassMemberChanges( classId: ClassId, - currentMemberSnapshots: List, - previousMemberSnapshots: List, + currentMemberSnapshots: List, + previousMemberSnapshots: List, changes: ChangeSet.Collector ) { - val currentMemberHashes: Map = currentMemberSnapshots.associateBy { it.abiHash } - val previousMemberHashes: Map = previousMemberSnapshots.associateBy { it.abiHash } + val currentMemberHashes: Map = currentMemberSnapshots.associateBy { it.abiHash } + val previousMemberHashes: Map = previousMemberSnapshots.associateBy { it.abiHash } val addedMembers = currentMemberHashes.keys - previousMemberHashes.keys val removedMembers = previousMemberHashes.keys - currentMemberHashes.keys diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt index 2f09e48757e..c425eb44040 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt @@ -6,7 +6,9 @@ package org.jetbrains.kotlin.incremental.classpathDiff import com.google.gson.GsonBuilder +import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL import org.jetbrains.kotlin.incremental.md5 +import org.jetbrains.kotlin.incremental.storage.toByteArray import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.tree.ClassNode @@ -14,7 +16,11 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode /** Computes a [JavaClassSnapshot] of a Java class. */ object JavaClassSnapshotter { - fun snapshot(classFile: ClassFileWithContents, includeDebugInfoInSnapshot: Boolean? = null): JavaClassSnapshot { + fun snapshot( + classFile: ClassFileWithContents, + granularity: ClassSnapshotGranularity, + includeDebugInfoInSnapshot: Boolean + ): JavaClassSnapshot { // We will extract ABI information from the given class and store it into the `abiClass` variable. // It is acceptable to collect more info than required, but it is incorrect to collect less info than required. // There are 2 approaches: @@ -52,9 +58,12 @@ object JavaClassSnapshotter { abiClass.methods.clear() val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) } + val detailedSnapshot = JavaClassMemberLevelSnapshot(classAbiExcludingMembers, fieldsAbi, methodsAbi) return JavaClassSnapshot( - classFile.classInfo.classId, classFile.classInfo.supertypes, - classAbiExcludingMembers, fieldsAbi, methodsAbi + classId = classFile.classInfo.classId, + classAbiHash = JavaClassMemberLevelSnapshotExternalizer.toByteArray(detailedSnapshot).md5(), + classMemberLevelSnapshot = detailedSnapshot.takeIf { granularity == CLASS_MEMBER_LEVEL }, + supertypes = classFile.classInfo.supertypes ) } @@ -73,15 +82,19 @@ object JavaClassSnapshotter { .create() } - private fun snapshotJavaElement(javaElement: Any, javaElementName: String, includeDebugInfoInSnapshot: Boolean? = null): AbiSnapshot { - return if (includeDebugInfoInSnapshot == true) { + private fun snapshotJavaElement( + javaElement: Any, + javaElementName: String, + includeDebugInfoInSnapshot: Boolean + ): JavaElementSnapshot { + return if (includeDebugInfoInSnapshot) { val abiValue = gsonForDebug.toJson(javaElement) val abiHash = abiValue.toByteArray().md5() - AbiSnapshotForTests(javaElementName, abiHash, abiValue) + JavaElementSnapshotForTests(javaElementName, abiHash, abiValue) } else { val abiValue = gson.toJson(javaElement) val abiHash = abiValue.toByteArray().md5() - AbiSnapshot(javaElementName, abiHash) + JavaElementSnapshot(javaElementName, abiHash) } } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ProgramSymbol.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ProgramSymbol.kt index bd09ad9b271..5484a7886f5 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ProgramSymbol.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ProgramSymbol.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.incremental.classpathDiff import org.jetbrains.kotlin.incremental.LookupSymbol -import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -21,6 +20,39 @@ data class ClassMember(val classId: ClassId, val memberName: String) : ProgramSy data class PackageMember(val packageFqName: FqName, val memberName: String) : ProgramSymbol() +/** Compact representation for a set of [PackageMember]s. */ +class PackageMemberSet(val packageToMembersMap: Map>) { + + operator fun contains(other: PackageMember): Boolean { + return packageToMembersMap[other.packageFqName]?.let { other.memberName in it } ?: false + } + + fun containsElementsIn(other: PackageMemberSet): Boolean { + return this.packageToMembersMap.keys.intersect(other.packageToMembersMap.keys).any { commonPackage -> + val otherMembers = other.packageToMembersMap[commonPackage]!! + this.packageToMembersMap[commonPackage]!!.any { it in otherMembers } + } + } +} + +fun Set.compact(): PackageMemberSet { + val map = mutableMapOf>() + forEach { + map.getOrPut(it.packageFqName) { mutableSetOf() }.add(it.memberName) + } + return PackageMemberSet(map) +} + +fun List.combine(): PackageMemberSet { + val combinedMap = mutableMapOf>() + forEach { + it.packageToMembersMap.forEach { (packageFqName, memberNames) -> + combinedMap.getOrPut(packageFqName) { mutableSetOf() }.addAll(memberNames) + } + } + return PackageMemberSet(combinedMap) +} + /** * Finds [LookupSymbol]s that potentially refer to classes on the given classpath, and returns the [ProgramSymbol]s corresponding to those * [LookupSymbol]s. @@ -30,24 +62,23 @@ data class PackageMember(val packageFqName: FqName, val memberName: String) : Pr * * The given classpath must not contain duplicate classes. * - * It's okay if the returned result is an over-approximation. + * It's okay to over-approximate the result. */ -internal fun Collection.filterLookupSymbols(classpath: List): List { - val (packageFacades, regularClasses) = classpath.partition { - it is KotlinClassSnapshot && it.classInfo.classKind != KotlinClassHeader.Kind.CLASS +internal fun Collection.filterLookupSymbols(classpath: List): List { + val regularClassesOnClasspath: List = classpath.mapNotNull { + when (it) { + is RegularKotlinClassSnapshot -> it.classId + is JavaClassSnapshot -> it.classId + is PackageFacadeKotlinClassSnapshot, is MultifileClassKotlinClassSnapshot -> null + } } - val regularClassesOnClasspath: List = regularClasses.map { it.getClassId() } - val packageMembersOnClasspath: Set = - packageFacades.flatMap { - if ((it as KotlinClassSnapshot).classInfo.classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS) { - // If classKind == MULTIFILE_CLASS, we don't have the information about its package members (see - // `KotlinClassSnapshot.packageMembers`'s kdoc). However, package members in a MULTIFILE_CLASS should be found in - // MULTIFILE_CLASS_PART classes, so it's okay to ignore MULTIFILE_CLASS here. - emptyList() - } else { - it.packageMembers!! - } - }.toSet() // Use Set for presence check + val packageMembersOnClasspath: PackageMemberSet = classpath.mapNotNull { + when (it) { + is RegularKotlinClassSnapshot, is JavaClassSnapshot -> null + is PackageFacadeKotlinClassSnapshot -> it.packageMembers + is MultifileClassKotlinClassSnapshot -> it.constants + } + }.combine() // It's rare but possible for 2 ClassIds to have the same FqName (e.g., ClassId `com/example/Foo` and ClassId `com/example.Foo` both // have FqName `com.example.Foo`. ClassId `com/example/Foo` indicates class `Foo` in package `com/example', whereas ClassId diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt index 9985351fd1d..1dff5f0775e 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt @@ -8,20 +8,22 @@ package org.jetbrains.kotlin.incremental.classpathDiff import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter import org.jetbrains.kotlin.incremental.ChangesEither import org.jetbrains.kotlin.incremental.LookupSymbol +import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_LEVEL +import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.asFile import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll +import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME import org.junit.Test import org.junit.rules.TemporaryFolder import java.io.File import kotlin.test.fail -abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() { +private val testDataDir = + File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest") - companion object { - val testDataDir = - File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest") - } +abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() { @Test abstract fun testAbiVersusNonAbiChanges() @@ -29,6 +31,12 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() { @Test abstract fun testModifiedAddedRemovedElements() + @Test + abstract fun testModifiedAddedRemovedElements_ClassLevelSnapshot() + + @Test + abstract fun testMixedClassSnapshotGranularities() + @Test abstract fun testImpactAnalysis() } @@ -80,6 +88,49 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { ).assertEquals(changes) } + @Test + override fun testModifiedAddedRemovedElements_ClassLevelSnapshot() { + val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/kotlin"), tmpDir, CLASS_LEVEL) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "ModifiedClassUnchangedMembers", scope = "com.example"), + LookupSymbol(name = "ModifiedClassChangedMembers", scope = "com.example"), + LookupSymbol(name = "AddedClass", scope = "com.example"), + LookupSymbol(name = "RemovedClass", scope = "com.example"), + ), + fqNames = setOf( + "com.example.ModifiedClassUnchangedMembers", + "com.example.ModifiedClassChangedMembers", + "com.example.AddedClass", + "com.example.RemovedClass", + ) + ).assertEquals(changes) + } + + @Test + override fun testMixedClassSnapshotGranularities() { + val currentClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("kotlin", "current-classpath", tmpDir) + val previousClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("kotlin", "previous-classpath", tmpDir) + + val changes = computeClasspathChanges(currentClasspathSnapshot, previousClasspathSnapshot) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"), + LookupSymbol(name = "CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class", scope = "com.example"), + LookupSymbol(name = "FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"), + LookupSymbol(name = "modifiedProperty", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"), + LookupSymbol(name = "modifiedFunction", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"), + LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class") + ), + fqNames = setOf( + "com.example.CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class", + "com.example.CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class", + "com.example.FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class", + "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class" + ) + ).assertEquals(changes) + } + @Test override fun testImpactAnalysis() { val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinOnly/src"), tmpDir) @@ -230,6 +281,49 @@ class JavaOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { ).assertEquals(changes) } + @Test + override fun testModifiedAddedRemovedElements_ClassLevelSnapshot() { + val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir, CLASS_LEVEL) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "ModifiedClassUnchangedMembers", scope = "com.example"), + LookupSymbol(name = "ModifiedClassChangedMembers", scope = "com.example"), + LookupSymbol(name = "AddedClass", scope = "com.example"), + LookupSymbol(name = "RemovedClass", scope = "com.example") + ), + fqNames = setOf( + "com.example.ModifiedClassUnchangedMembers", + "com.example.ModifiedClassChangedMembers", + "com.example.AddedClass", + "com.example.RemovedClass" + ) + ).assertEquals(changes) + } + + @Test + override fun testMixedClassSnapshotGranularities() { + val currentClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("java", "current-classpath", tmpDir) + val previousClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("java", "previous-classpath", tmpDir) + + val changes = computeClasspathChanges(currentClasspathSnapshot, previousClasspathSnapshot) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"), + LookupSymbol(name = "CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class", scope = "com.example"), + LookupSymbol(name = "FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"), + LookupSymbol(name = "modifiedField", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"), + LookupSymbol(name = "modifiedMethod", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"), + LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class") + ), + fqNames = setOf( + "com.example.CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class", + "com.example.CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class", + "com.example.FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class", + "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class" + ) + ).assertEquals(changes) + } + @Test override fun testImpactAnalysis() { val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir) @@ -262,7 +356,7 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon() @Test fun testImpactAnalysis() { val changes = - computeClasspathChanges(File(ClasspathChangesComputerTest.testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir) + computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir) Changes( lookupSymbols = setOf( LookupSymbol(name = "changedProperty", scope = "com.example.ChangedKotlinSuperClass"), @@ -296,25 +390,44 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon() } } -private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder): Changes { - val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir) - val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir) - return computeClasspathChanges(currentSnapshot, previousSnapshot) +private fun testMixedClassSnapshotGranularities_snapshotClasspath( + language: String, classpathSourceDirName: String, tmpDir: TemporaryFolder +): ClasspathSnapshot { + val classes = compileAll(File("$testDataDir/testMixedClassSnapshotGranularities/src/$language/$classpathSourceDirName/0"), tmpDir) + + fun getGranularity(classFile: ClassFile): ClassSnapshotGranularity { + val granularity = when (val className = classFile.asFile().nameWithoutExtension) { + "CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class" -> CLASS_LEVEL to CLASS_LEVEL + "CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class" -> CLASS_LEVEL to CLASS_MEMBER_LEVEL + "FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class" -> CLASS_MEMBER_LEVEL to CLASS_LEVEL + "FineGrainedFirstBuild_FineGrainedSecondBuild_Class" -> CLASS_MEMBER_LEVEL to CLASS_MEMBER_LEVEL + else -> error("Unrecognized class: $className") + } + return when (classpathSourceDirName) { + "previous-classpath" -> granularity.first + "current-classpath" -> granularity.second + else -> error("Unrecognized classpathSourceDirName: $classpathSourceDirName") + } + } + + return classes.map { it.snapshot(getGranularity(it)) }.toClasspathSnapshot() } -private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder): ClasspathSnapshot { - val classpath = mutableListOf() - val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir -> - val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir) - classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot)) +private fun List.toClasspathSnapshot(): ClasspathSnapshot { + val classpathEntrySnapshot = ClasspathEntrySnapshot(associateByTo(LinkedHashMap()) { + JvmClassName.byClassId((it as AccessibleClassSnapshot).classId).internalName + ".class" + }) + return ClasspathSnapshot(listOf(classpathEntrySnapshot)) +} - val relativePaths = classFiles.map { it.unixStyleRelativePath } - val classSnapshots = classFiles.snapshot().map { it.withHash } - ClasspathEntrySnapshot( - classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap()) - ) - } - return ClasspathSnapshot(classpathEntrySnapshots) +private fun computeClasspathChanges( + classpathSourceDir: File, + tmpDir: TemporaryFolder, + granularity: ClassSnapshotGranularity? = null +): Changes { + val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, granularity) + val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, granularity) + return computeClasspathChanges(currentSnapshot, previousSnapshot) } private fun computeClasspathChanges( diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializerTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializerTest.kt index 011f9523647..fa452267c3b 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializerTest.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializerTest.kt @@ -36,8 +36,8 @@ class KotlinClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializer override val sourceFile = TestSourceFile( KotlinSourceFile( - baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt", - preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class") + baseDir = File(testDataDir, "kotlin/testSimpleClass/src"), relativePath = "com/example/SimpleClass.kt", + preCompiledClassFile = ClassFile(File(testDataDir, "kotlin/testSimpleClass/classes"), "com/example/SimpleClass.class") ), tmpDir ) } @@ -46,7 +46,7 @@ class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTe override val sourceFile = TestSourceFile( JavaSourceFile( - baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java", + baseDir = File(testDataDir, "java/testSimpleClass/src"), relativePath = "com/example/SimpleClass.java", ), tmpDir ) } diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotTestCommon.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotTestCommon.kt index 8d728b6f0f3..46584c3b3e6 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotTestCommon.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotTestCommon.kt @@ -6,14 +6,17 @@ package org.jetbrains.kotlin.incremental.classpathDiff import com.google.gson.GsonBuilder +import org.jetbrains.kotlin.cli.common.isWindows import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.asFile import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compile +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.Rule import org.junit.rules.TemporaryFolder import java.io.File +import java.lang.ProcessBuilder.Redirect abstract class ClasspathSnapshotTestCommon { @@ -68,12 +71,12 @@ abstract class ClasspathSnapshotTestCommon { } else { val srcDir = tmpDir.newFolder() asFile().copyTo(File(srcDir, unixStyleRelativePath)) - compileAll(srcDir, classpath = emptyList(), tmpDir) + compileAll(srcDir, tmpDir) } } /** Compiles the source files in the given directory and returns all generated .class files. */ - fun compileAll(srcDir: File, classpath: List, tmpDir: TemporaryFolder): List { + fun compileAll(srcDir: File, tmpDir: TemporaryFolder, classpath: List = emptyList()): List { val kotlinClasses = compileKotlin(srcDir, classpath, tmpDir) val javaClasspath = classpath + listOfNotNull(kotlinClasses.firstOrNull()?.classRoot) @@ -126,11 +129,20 @@ abstract class ClasspathSnapshotTestCommon { } val classesDir = tmpDir.newFolder() - org.jetbrains.kotlin.test.MockLibraryUtil.compileKotlin( + // Note: Calling the following is simpler: + // org.jetbrains.kotlin.test.MockLibraryUtil.compileKotlin( + // srcDir.path, classesDir, extraClasspath = classpath.map { it.path }.toTypedArray()) + // However, it currently fails with UnsupportedClassVersionError, so we have to launch a new kotlinc process instead. + val kotlincBinary = if (isWindows) "dist/kotlinc/bin/kotlinc.bat" else "dist/kotlinc/bin/kotlinc" + check(File(kotlincBinary).exists()) { "'${File(kotlincBinary).absolutePath}' not found. Run ./gradlew dist first." } + val commandAndArgs = listOf( + kotlincBinary, srcDir.path, - classesDir, - extraClasspath = classpath.map { it.path }.toTypedArray() + "-d", classesDir.path, + "-classpath", (listOf(srcDir) + classpath).joinToString(File.pathSeparator) { it.path } ) + runCommandInNewProcess(commandAndArgs) + return getClassFilesInDir(classesDir) } @@ -164,11 +176,52 @@ abstract class ClasspathSnapshotTestCommon { fun ClassFile.readBytes() = asFile().readBytes() - fun ClassFile.snapshot(): ClassSnapshot = listOf(this).snapshot().single() + fun ClassFile.snapshot(granularity: ClassSnapshotGranularity? = null): ClassSnapshot = listOf(this).snapshot(granularity).single() - fun List.snapshot(): List { - val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) } - return ClassSnapshotter.snapshot(classFilesWithContents) + fun List.snapshot(granularity: ClassSnapshotGranularity? = null): List { + val classes = map { ClassFileWithContents(it, it.readBytes()) } + return if (granularity == null) { + ClassSnapshotter.snapshot(classes) + } else { + ClassSnapshotter.snapshot(classes, granularity = granularity) + } } } } + +internal fun snapshotClasspath( + classpathSourceDir: File, + tmpDir: TemporaryFolder, + granularity: ClassSnapshotGranularity? = null +): ClasspathSnapshot { + val classpath = mutableListOf() + val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir -> + val classFiles = compileAll(classpathEntrySourceDir, tmpDir, classpath) + classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot)) + + val relativePaths = classFiles.map { it.unixStyleRelativePath } + val classSnapshots = classFiles.snapshot(granularity) + ClasspathEntrySnapshot( + classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap()) + ) + } + return ClasspathSnapshot(classpathEntrySnapshots) +} + +private fun runCommandInNewProcess(commandAndArgs: List) { + val processBuilder = ProcessBuilder(commandAndArgs) + processBuilder.redirectInput(Redirect.INHERIT) + processBuilder.redirectOutput(Redirect.INHERIT) + processBuilder.redirectErrorStream(true) + val process = processBuilder.start() + + val exitCode = try { + process.waitFor() + } finally { + process.destroyForcibly() + } + check(exitCode == 0) { + "Process returned exit code: $exitCode\n" + + "commandAndArgs = ${commandAndArgs.joinToString(" ")}" + } +} diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest.kt index c3c26fe3b49..315a3f27599 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest.kt @@ -6,94 +6,159 @@ package org.jetbrains.kotlin.incremental.classpathDiff import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.readBytes +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.JavaSourceFile import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile +import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.TestSourceFile import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals import org.junit.Test +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue import java.io.File -abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() { +private val testDataDir = + File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest") - companion object { - val testDataDir = - File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest") - } +class KotlinOnlyClasspathSnapshotterTest : ClasspathSnapshotTestCommon() { - protected abstract val sourceFile: TestSourceFile - protected abstract val sourceFileWithAbiChange: TestSourceFile - protected abstract val sourceFileWithNonAbiChange: TestSourceFile - - private val expectedSnapshotFile: File - get() = sourceFile.asFile().let { - val srcDir = File(it.path.substringBeforeLast("src") + "src") - val relativePath = it.relativeTo(srcDir) - testDataDir.resolve("expected-snapshot").resolve(relativePath.parent).resolve(relativePath.nameWithoutExtension + ".json") - } + @Suppress("SameParameterValue") + private fun getSourceFile(testName: String, relativePath: String) = TestSourceFile( + KotlinSourceFile( + baseDir = File("$testDataDir/kotlin/$testName/src"), relativePath = relativePath, + preCompiledClassFile = ClassFile(File("$testDataDir/kotlin/$testName/classes"), relativePath.replace(".kt", ".class")) + ), tmpDir + ) @Test - fun `test ClassSnapshotter's result against expected snapshot`() { - val classSnapshot = sourceFile.compileSingle().let { - ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInJavaSnapshot = true) - }.single() + fun testSimpleClass() { + val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.kt") + val actualSnapshot = sourceFile.compileAndSnapshot().toGson() + val expectedSnapshot = sourceFile.getExpectedSnapshotFile().readText() - assertEquals(expectedSnapshotFile.readText(), classSnapshot.toGson()) + assertEquals(expectedSnapshot, actualSnapshot) + + // Check that the snapshot contains ABI info + actualSnapshot.assertContains("publicProperty", "publicFunction") + + // Private properties and functions' names/signatures are currently part of the snapshot. We will fix this later. + actualSnapshot.assertContains("privateProperty", "privateFunction") + + // Check that the snapshot does not contain non-ABI info + actualSnapshot.assertDoesNotContain( + "publicProperty's value", + "privateProperty's value", + "publicFunction's body", + "privateFunction's body" + ) } @Test - fun `test ClassSnapshotter extracts ABI info from a class`() { - // After an ABI change, the snapshot must change - assertNotEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithAbiChange.compileAndSnapshot().toGson()) + fun testSimpleClass_ClassLevelSnapshot() { + val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.kt") + val classFile = sourceFile.compileSingle() + val actualSnapshot = classFile.snapshot(ClassSnapshotGranularity.CLASS_LEVEL).toGson() + val expectedSnapshot = sourceFile.getExpectedSnapshotFile(ClassSnapshotGranularity.CLASS_LEVEL).readText() + + assertEquals(expectedSnapshot, actualSnapshot) + + // Check that the snapshot does not contain class member details + actualSnapshot.assertDoesNotContain("publicProperty", "privateProperty", "publicFunction", "privateFunction") } @Test - fun `test ClassSnapshotter does not extract non-ABI info from a class`() { - // After a non-ABI change, the snapshot must not change - assertEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithNonAbiChange.compileAndSnapshot().toGson()) + fun testPackageFacadeClasses() { + val classpathSnapshot = snapshotClasspath(File("$testDataDir/kotlin/testPackageFacadeClasses/src"), tmpDir) + val classSnapshots = classpathSnapshot.classpathEntrySnapshots.single().classSnapshots + val fileFacadeSnapshot = classSnapshots["com/example/FileFacadeKt.class"]!!.toGson() + val multifileClassSnapshot = classSnapshots["com/example/MultifileClass.class"]!!.toGson() + val multifileClassPart1Snapshot = classSnapshots["com/example/MultifileClass__MultifileClass1Kt.class"]!!.toGson() + val multifileClassPart2Snapshot = classSnapshots["com/example/MultifileClass__MultifileClass2Kt.class"]!!.toGson() + + // Check that the snapshots contain ABI info + fileFacadeSnapshot.assertContains("propertyInFileFacade", "functionInFileFacade") + multifileClassPart1Snapshot.assertContains("propertyInMultifileClass1", "functionInMultifileClass1") + multifileClassPart2Snapshot.assertContains("propertyInMultifileClass2", "functionInMultifileClass2") + + // Check that the snapshots do not contain non-ABI info + fileFacadeSnapshot.assertDoesNotContain("propertyInFileFacade's value", "functionInFileFacade's body") + multifileClassPart1Snapshot.assertDoesNotContain("propertyInMultifileClass1's value", "functionInMultifileClass1's body") + multifileClassPart2Snapshot.assertDoesNotContain("propertyInMultifileClass2's value", "functionInMultifileClass2's body") + + // Classes with MULTIFILE_CLASS kind have no proto data + multifileClassSnapshot.assertDoesNotContain( + "propertyInMultifileClass1", + "functionInMultifileClass1", + "propertyInMultifileClass2", + "functionInMultifileClass2" + ) } } -class KotlinOnlyClasspathSnapshotterTest : ClasspathSnapshotterTest() { +class JavaOnlyClasspathSnapshotterTest : ClasspathSnapshotTestCommon() { - override val sourceFile = TestSourceFile( - KotlinSourceFile( - baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt", - preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class") - ), tmpDir + @Suppress("SameParameterValue") + private fun getSourceFile(testName: String, relativePath: String) = TestSourceFile( + JavaSourceFile(baseDir = File("$testDataDir/java/$testName/src"), relativePath = relativePath), tmpDir ) - override val sourceFileWithAbiChange = TestSourceFile( - KotlinSourceFile( - baseDir = File(testDataDir, "src-changed/kotlin/abi-change"), relativePath = "com/example/SimpleClass.kt", - preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotllin/abi-change"), "com/example/SimpleClass.class") - ), tmpDir - ) + private fun TestSourceFile.compileAndSnapshotWithDebugInfo(): ClassSnapshot { + val classFile = compileSingle() + return ClassSnapshotter.snapshot( + listOf(ClassFileWithContents(classFile, classFile.readBytes())), + includeDebugInfoInJavaSnapshot = true + ).single() + } - override val sourceFileWithNonAbiChange = TestSourceFile( - KotlinSourceFile( - baseDir = File(testDataDir, "src-changed/kotlin/non-abi-change"), relativePath = "com/example/SimpleClass.kt", - preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotlin/non-abi-change"), "com/example/SimpleClass.class") - ), tmpDir - ) + @Test + fun testSimpleClass() { + val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.java") + val actualSnapshot = sourceFile.compileAndSnapshotWithDebugInfo().toGson() + val expectedSnapshot = sourceFile.getExpectedSnapshotFile().readText() + + assertEquals(expectedSnapshot, actualSnapshot) + + // Check that the snapshot contains ABI info + actualSnapshot.assertContains("publicField", "publicMethod") + + // Check that the snapshot does not contain non-ABI info + actualSnapshot.assertDoesNotContain( + "privateField", + "privateMethod", + "publicField's value", + "privateField's value", + "publicMethod's body", + "privateMethod's body" + ) + } + + @Test + fun testSimpleClass_ClassLevelSnapshot() { + val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.java") + val classFile = sourceFile.compileSingle() + val actualSnapshot = classFile.snapshot(ClassSnapshotGranularity.CLASS_LEVEL).toGson() + val expectedSnapshot = sourceFile.getExpectedSnapshotFile(ClassSnapshotGranularity.CLASS_LEVEL).readText() + + assertEquals(expectedSnapshot, actualSnapshot) + + // Check that the snapshot does not contain class member details + actualSnapshot.assertDoesNotContain("publicField", "privateField", "publicMethod", "privateMethod") + } } -class JavaOnlyClasspathSnapshotterTest : ClasspathSnapshotterTest() { - - override val sourceFile = TestSourceFile( - JavaSourceFile( - baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java", - ), tmpDir - ) - - override val sourceFileWithAbiChange = TestSourceFile( - JavaSourceFile( - baseDir = File(testDataDir, "src-changed/java/abi-change"), relativePath = "com/example/SimpleClass.java", - ), tmpDir - ) - - override val sourceFileWithNonAbiChange = TestSourceFile( - JavaSourceFile( - baseDir = File(testDataDir, "src-changed/java/non-abi-change"), relativePath = "com/example/SimpleClass.java", - ), tmpDir - ) +private fun TestSourceFile.getExpectedSnapshotFile(granularity: ClassSnapshotGranularity? = null): File { + val relativePath = sourceFile.unixStyleRelativePath.substringBeforeLast(".") + ".json" + val expectedSnapshotDirName = if (granularity == null) "expected-snapshot" else "expected-snapshot-${granularity.name}" + return sourceFile.baseDir.resolve("../$expectedSnapshotDirName/$relativePath") +} + +private fun String.assertContains(vararg elements: String) { + elements.forEach { + assertTrue(contains(it)) + } +} + +private fun String.assertDoesNotContain(vararg elements: String) { + elements.forEach { + assertFalse(contains(it)) + } } diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/current-classpath/0/com/example/SomeClass$CompanionObject.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/current-classpath/0/com/example/SomeClass$CompanionObject.class index 7d737623d52c045fdc8ed726c89c47f3d023778a..dfd6d906592fa603687f22cd39bff9adc7ff2c57 100644 GIT binary patch delta 13 UcmX@gew2NKATuM!WFh9w03B5W!2kdN delta 13 UcmX@gew2NKATuNTWFh9w03A>RzyJUM diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/current-classpath/0/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/current-classpath/0/com/example/SomeClass.class index 1f9879b47b15e5506df98f01ce139aa7766d7342..81da98ed4b5a0e7f32899b075d40a27dd5620bf5 100644 GIT binary patch delta 74 zcmbQuJD+#MAr>WL21X!cU|`kS&cLw|$Y5fy1d?1pnt_3tfrWu(@&guYM*hh%tnQOn avhqyMXSJIAo|SL%c2)~Up~)XuR|5d;f)Bs| delta 70 zcmbQwJDYdIAr?7(21X!cU|`kS&cLw|$Y5fy1d?oELFUO%SgaZOCM&SIPnKupnOw|j U#V9a&9joi)wXA%T8QJCm0Km!(@c;k- diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/previous-classpath/0/com/example/SomeClass$CompanionObject.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/previous-classpath/0/com/example/SomeClass$CompanionObject.class index c358e66dcb84fde0280a09d8f3cc835abbd6903e..5922beb2e1e6d62a2e65f69a463d223378de93bb 100644 GIT binary patch delta 13 UcmdnZzMFjm7c(QrWNzjS02~zqoB#j- delta 13 UcmdnZzMFjm7c(RKWNzjS02~kln*aa+ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/previous-classpath/0/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testConstantsAndInlineFunctions_KotlinOnly/classes/previous-classpath/0/com/example/SomeClass.class index 1ecc638ce5840ef3b5eb76778a1b4bd17aac3eaf..59d8750a284e4baaf07686c398d7733f9859fd35 100644 GIT binary patch delta 74 zcmey)^P6YGViqN121X!cU|`kS&cLw|$Y5eH0g_xmnt_3tfrWu(@a^`6g@K!9`H)-gQ8OW{#d9nLK`&lVItXR85sO62 zr-@LJ44Nv@rao3-@cLALR}SJRm=8jsbq0*V-xr6X8j5JIy4~NG1CxQn;6753GLIRY zTerJ;q~Ur*hfHHOxHf=+4XJ;mY*ix^tTQ~DPD&mx zbC3JeiN~`R12b~SUr9G!R0=0yMCs^YpI=*@7Vn6`28kLvfgx3NoCW%YtP$Y zR^`Dd`~Ue{?hOt3+=v6ou+pI)eLf!bW%5$=L+TVe@j!%Kktkcwo8E3bP6o26?8e&8 zI5KJ^yGkosc@jmj5!N4#b#g8>bD(IAB;?RN>31n!p{i=Vp`5<+1=&voIIdE(W4n5d zV*X?SIe1hRur%wOkolba8U9BaE|OD!B^Q2?@6xsv;kn;aA%W_a^`6g@K!9`H)-gQ8OW{#d9nLK_no8lzxP2(6|y#Ds+zcw?RNc&~XgG??1kN18^7D86FMdQB@v`(P1d7jaVdF zK23y*WYAQJHubRzgV#-}7u`KMh@)WMZiGVX3>brdAdW;e6w!Y5dH+BTOa=~v`#?p? zJY;Zg-0bF&hU+1P3`5GiRhpsR`Cs$<4D0*SY{!9mqh#`zJKXdeccIl}-Du zJ#ULymwW&6C3l8~cHD>q$*|U;zuOs)`Z9Sb`XP0Sop>O^u1J)v7fo+39w!6YRF<)^ zJC2MR$*$6hD37BkHo|U4W0Urln!Ueh*GR~rThh-`yhK&iZiaID))!4Oe}?~&hRfpAU&V!A;@iZwCOr4MGQFha3tNt>v-2dSDvoaK4AEhNp2s!_`=wffbFsM&TznUa>g< literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/current-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/current-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class new file mode 100644 index 0000000000000000000000000000000000000000..22aa9a9cf4bef8fc3c22c9ca72d0c7c7b02d6465 GIT binary patch literal 853 zcmbtR&u^3_?Q*WI7 zL%e%1F){IANRR$e#y1oVQBQh#?|bik-+VJ~zJ34l6~Jv=Wq1%Iqq;m4qy0$M+bWii zQ$aCot5lojSViHhRw7dUv%e#QBo60;NNAk{WAOLHfv86!-mO0y?8(67;4rxNRIJQ> z2It!KUJ+Ti9x})=WXv0-8JgYyHNVTSx+~3A5~|lqhR@SvU#8~m%yE;hHTq+~Hj7xs zi2^uQ76|Qe92k|v45jWcF_DVvThfS77(pIV)A1Yfl2!-nmPo|TK z$E)1q{&eE;yaffyPIWrj;0@Ny9=&(D&o^B^SL2P$cExvWetGon5l!;mbW+RYv%YK3 z+hEq^&cA%gt)ZbEw~|mYtaRx&pN>ZZnZ6K%h&ttN5{RfLQf2FT)7weLX&~FmGS;@o zu~8%0Q(6(_VH_t$*zIVn)7~<(_m}M&2|092`dNw>sH)q|P|n`?jQmFe92Y6tv87(3 zSUe`60FSB?mS&w}F`tV+!T&(RMRDe@;?ghiO=4RUUie*^T~PA5Eyv~A`R8$ka*LE@ tdc|rVP6w3^PIhn#rwO9k!5V6PFpV=f+eco*(>SN$YAnydl15RZ^aCgo7D&$2w)%ZFYvH-Z=S( zc=uppV&cJ&9{r8`x3~~$^^Hynwhn@eEzR$3_FU?L8sy9l8FVf^trsmyLZ<{VP{A0nki&(~~0ytI{ z0Ig9R7?s2frOqHRk&5d((uhzPK^lB`SVmZ8*D3k^1)yezEw8?vuaV?Y2`mQ~1gISe( z3&#KBcib5m^0%3Ul3}Go|M+Y)?923}=tnducalIvU6Cr=&xhV#GD-v4QWmkcJBp1O z%C6FiAdliWF~WMIu}-dKrmrts6RC3Oj`XV(FHu*wzEIBI`hxr?DmX4vv}+5!La{gl zpa74$5|*Zu8JW+?pW%O`ibigR@kk+Qu4cJur=PINw8F!_&B+;c6^TizSVsM(HPAM6RL$ literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class new file mode 100644 index 0000000000000000000000000000000000000000..59ddcc001b41e5005900ae32f395feef565da090 GIT binary patch literal 859 zcmbtRO=}ZD7=C6p*`(XlZqle}{Qfwoh)IhVQ3R6`T}iF95`-LNk{#aK@dDh=+Pe~z7vBa>Ot`GzR&yd%ro=Mx9?xR0=SFo42^y=tjS|BJd9+mnTS-& z$Ei@U3|lJIrZG}c_-diQC;Lep&IXatItRw!ABZDSi$uI%+vy$1zRAI1a382xnTHI{ zjhmeUvT!|QkYmW0H%c>Xwf|rKKEv9+G}}q2UMm?sPm@ELnzvKUfG#%tW59v}R&Z(w z9IJB+tx?=JDv242?LlH971y?<5uq@GH2CnyA=8)*t_@&d1L_|uTh$4L8w^h-dCE>@GT!8M*2tc`ce&4>x_++8>zUoM@7VnE5p_!Kq%We5NR_Q;O>Zw5rG42_c4K{a z6dN^^9iFVtLwGB!7bcfrj(s%wNgHU*y{}W$ygW%+4|SOqS#7^!$ssM!8Ac61`%r p3nxH1!07;IaF!q{0oGCNf@z$?`7ZJrp2h_YS7T)gmNg0*#UBtWv%>%Y literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class.class new file mode 100644 index 0000000000000000000000000000000000000000..1efac2986398d0282204a3085cd61b060fcd2c8c GIT binary patch literal 853 zcmbtRO=}ZD7=C7Uvq`t9-K0^|`2BHE5jQP@2SqfIXe6!DN)U38Np`H$Y^jK+^==V4cs^X@8C>&5X@*AUf5q=Itn5g$kw)sZlF{=l-IJMlJ5y}awMKsoSi6WN zoLB(I^09^1I2jm~CJd#{Fg3AC!VPIeB#a;pKH7K4FlK{i0~pwl`UlEZbwbe^!;|Tx z;`1u^c`%*$ykJ3rvQwQ-)_I*Za)<9d9`L7LkgxH&yHyDsn_nKjdqk6hKb_RvLN4&^ zdF#xo-1(O;xivK8aWjo1!*YlIZgV{9%j|{d$J8lz(t(J(B2%`WH~sB&oDF14*^Sk$ zabna+c9mAN@-RtKBdj|bYvh|ddw<#5NXVgE($7-7Kvig+p`5$%8HJAoI4)AOW4n5Z zV)1AJ3-GBbVR6>^mHY|82O1tHyMHB@ev)s_mAUgfGk1*1=dv7^XXl^C70ONGmgyBM qJvePt+Bn(9DV!#VY8$Jl^}saF;A{^C4PWD&hNrPK1B)6(jnWS*g|RFE literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/testMixedClassSnapshotGranularities/classes/kotlin/previous-classpath/0/com/example/FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class.class new file mode 100644 index 0000000000000000000000000000000000000000..2ce911e1c45d0be8c31dc432176b48ed47123233 GIT binary patch literal 853 zcmbtR%We}f6g~D#Ch0V#lQcjY-j4+!#H1Ax3lM??1%lFoS`;B=ft-vfZYDF;*s01c zD?WrZ8zdwo7AWeHk3w9BfXZV9n|sfF93T7m%hykz0o=r8hI@l_6v_iJ+KXk_Qi*(+ z35romW!f~xDvn+>Q<3Q({cSl&lW0DOh1Pj6hG19hi!c_+PWYt1D+eYIhrzp}5@qf( zI9INAi^#$A;UdrAnpa9QG&=t)ew$%sN1BZ^QZJQ^o@VKu%*^YVVwBQ#+3ksB->U6Ts>#UJGeCzRmKlXxrjo00+O5oW1{P4{KniTx$q~;cKfoIQK zXIACbzkJD!p&^f(X(SnzJM^2I<56E`&qP0_PPvl~MBEjbvh}>_Z>QsIAY00AtZt1H zqeil;w4#;!Ns=02-O*Sh-`v^z%hpCh4&9P|mf|_8LhB6W+_jG=yeGhMo}wMw)e97h z-xsg|pQ;iTXPsZk9}v8w;Zd^tS90km`TAU$JHIn?N0@vr%W-jb{#jh2+$3(9Ua``H p(?+F@<87S4NrI@hv5Hy`Oyd+z_fXLAHO^>w8cQ>a~*5^DNz$nRz?aYtyBMe+*c=ge9C<0LSv& zLTi+SMx_Zuxid&jtdd|$8W9O2NP`a#95Rbpa4mp=4XA&pY}KGFT4Q)T8CN}C;~w`X zV~-bYQl#kACgXM9V4L})_b&JO6W1@)c_X)7^&J~u9=&@=lcF~n*K@_Z@7nX$nN_(n zXZ$~Y$E|@Of17C}8J0Wrk55O#zRX^TeoUQ8Ck;j16`8X2tm*Bfqb!syWj9v0M~P8G z*;QK6%7Y|Hjj-NmtdVQE>FX=jM9LhxBmFAj1*!t;3&s46&nSMRgySNi9oyAQgr(yJ zEWo3xjKyi^SMn$LA80sF&i$2K{z<+$Q|8X^%={da&ty3+PtQM(D-@f=t6y$~1k$x19s)Xz!GOV@WvEhtJ&PAo}Hv7Rinh>SOXM*C}xA0093e^NwOZKP#cIrrZOT-geYQTPy!0`KqMJBf%bq5Q3jLp z3@Sj{3&lF;X^Pg5F*<9(ComvK6J~I5cV? zTS_Y`c^QV05mq&g4N@~kXL97NxD@5FHuo`Ff_^30TZC4uD&*sjej@pOPPk3hJ_8Q! zkj*R#kb*~94(U0vAoC^p2mEjJ$Za{mBKXO$}e diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotlin/non-abi-change/com/example/SimpleClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotlin/non-abi-change/com/example/SimpleClass.class deleted file mode 100644 index a535d512244ea4a9168ec3c9a0f772ee5dd0391c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 901 zcma)3OHWfl6#i!Jqqmn*ZcCAZ?>DlbK-_6e6hpkEDgi@Gx|+5V9Jsx==}Zak8W;W$ z*KSNSCN50F(qH1QFrJ|m2+_pNd3XgCOY zV1t0XR~-&k`7(Rr9ewb5#5aN{nCHvxGrr+Rc^hpNBggvJNAI56ns7MSszlxhdT*hC zpm{g&BibHsuQDs4c;*UA`-XP2mbE2)s;*M`qTgxC-Yd~e2r1XImPmF)PuYA7hTB=c z*OKeXR?KbnQ=>X^M`=YRkJB_W!tO|8g^uFVH#y4oMiiCpJ;=Kxf{se|CS@^IkS*yMeppW14({^vyQl#HhxY pi!qOj6yj2h%ec}2)3}Oj4TKscjq4h|#^iC3*C=aDYs_d=eghUmoE!iE diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotllin/abi-change/com/example/SimpleClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes-changed/kotllin/abi-change/com/example/SimpleClass.class deleted file mode 100644 index 844af60331ec18b3721c67e6b61a35757271d372..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 861 zcmZ`$TW=CU7(Fu!3oMi_6fNNW8Z|~}lF;X^Pg5F*<9(ComvK6J~I5cV? zTS_Y`c^QV05mq&g4N@~kXL97NxD@5FHuo`Ff_^30TZC4uD&*sjej@pOPPk3hJ_8Q! zkj*R#kb*~94(U0vAoC^p2mEjJ$Za{mBKXO$}e diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes/kotlin/com/example/SimpleClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/classes/kotlin/com/example/SimpleClass.class deleted file mode 100644 index d8e3a73804d229991c9f7ca2910b4b811ea837c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 861 zcmZ`$TWb?R7(FwaY&P38-K5bb@qR@`Oj>+X1g#XeL@lWlAy1QJsMBnALuL}{Tkydj z;4hwwy=DcGvE0xXMg|s`3t~f++|qlB>kG)7yUsbYun1Y^+;%) z0b>aE#8}iK5%1RCw)bSmWZ*EkPgShUGY040{hb`raDAkZVMv(|N;6a%=k$ESP}r4b zYt)WZXKR=YdWnft zTzf5z=n5lf1KuAybUd@hwFV5VNBF+7S)HQpDubL(NX>0uHq*)5mhtOvWc?4Bco*R&6yt~Hi z3i%V-Kk6A0b3N%wIl8%UW#8BCM4*J{4XxNb(%2rgGqu8jv z+)-Ll$@4f)jIgR{tdg23I+LSl#igi-<(W^>67(y{-XOGQRUw~#@B`kr8Q~^b`wTd^ zMK*U>fGm8<3RsvS|B}BW_)3o)$K>>xQSQmFSOuUzdQ4Mhkms?;OS8%ljU)%4RhO4nW3l=pNG;$h6 Gjly3!DwP%h diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/expected-snapshot/java/com/example/SimpleClass.json b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/expected-snapshot/java/com/example/SimpleClass.json deleted file mode 100644 index 3f0445a1c64..00000000000 --- a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/expected-snapshot/java/com/example/SimpleClass.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "classId": { - "packageFqName": { - "fqName": { - "fqName": "com.example" - } - }, - "relativeClassName": { - "fqName": { - "fqName": "SimpleClass" - } - }, - "local": false - }, - "supertypes": [ - { - "internalName": "java/lang/Object" - } - ], - "classAbiExcludingMembers": { - "abiValue": "{\n \"version\": 52,\n \"access\": 32,\n \"name\": \"com/example/SimpleClass\",\n \"superName\": \"java/lang/Object\",\n \"interfaces\": [],\n \"sourceFile\": \"SimpleClass.java\",\n \"innerClasses\": [],\n \"fields\": [],\n \"methods\": [],\n \"api\": 589824\n}", - "name": "com/example/SimpleClass", - "abiHash": 5093067435262353961 - }, - "fieldsAbi": [ - { - "abiValue": "{\n \"access\": 1,\n \"name\": \"publicField\",\n \"desc\": \"I\",\n \"api\": 589824\n}", - "name": "publicField", - "abiHash": -4066101678319939363 - } - ], - "methodsAbi": [ - { - "abiValue": "{\n \"access\": 0,\n \"name\": \"\\u003cinit\\u003e\",\n \"desc\": \"()V\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}", - "name": "\u003cinit\u003e", - "abiHash": -8222317811013429324 - }, - { - "abiValue": "{\n \"access\": 1,\n \"name\": \"publicMethod\",\n \"desc\": \"()I\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}", - "name": "publicMethod", - "abiHash": -4555436938653125191 - } - ], - "className$delegate": { - "initializer": {}, - "_value": {} - }, - "withHash$delegate": { - "initializer": {}, - "_value": {} - } -} \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json new file mode 100644 index 00000000000..c837712e9b1 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json @@ -0,0 +1,21 @@ +{ + "classId": { + "packageFqName": { + "fqName": { + "fqName": "com.example" + } + }, + "relativeClassName": { + "fqName": { + "fqName": "SimpleClass" + } + }, + "local": false + }, + "classAbiHash": 2419143040117344894, + "supertypes": [ + { + "internalName": "java/lang/Object" + } + ] +} \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json new file mode 100644 index 00000000000..64b0157e4e9 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json @@ -0,0 +1,47 @@ +{ + "classId": { + "packageFqName": { + "fqName": { + "fqName": "com.example" + } + }, + "relativeClassName": { + "fqName": { + "fqName": "SimpleClass" + } + }, + "local": false + }, + "classAbiHash": 7253633932031152470, + "classMemberLevelSnapshot": { + "classAbiExcludingMembers": { + "abiValue": "{\n \"version\": 52,\n \"access\": 33,\n \"name\": \"com/example/SimpleClass\",\n \"superName\": \"java/lang/Object\",\n \"interfaces\": [],\n \"sourceFile\": \"SimpleClass.java\",\n \"innerClasses\": [],\n \"fields\": [],\n \"methods\": [],\n \"api\": 589824\n}", + "name": "com/example/SimpleClass", + "abiHash": -1986494095366785349 + }, + "fieldsAbi": [ + { + "abiValue": "{\n \"access\": 1,\n \"name\": \"publicField\",\n \"desc\": \"Ljava/lang/String;\",\n \"api\": 589824\n}", + "name": "publicField", + "abiHash": 1021768301411937004 + } + ], + "methodsAbi": [ + { + "abiValue": "{\n \"access\": 1,\n \"name\": \"\\u003cinit\\u003e\",\n \"desc\": \"()V\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}", + "name": "\u003cinit\u003e", + "abiHash": 5725188035643409224 + }, + { + "abiValue": "{\n \"access\": 1,\n \"name\": \"publicMethod\",\n \"desc\": \"()Ljava/lang/String;\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}", + "name": "publicMethod", + "abiHash": 8210873051092995586 + } + ] + }, + "supertypes": [ + { + "internalName": "java/lang/Object" + } + ] +} \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/src/com/example/SimpleClass.java b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/src/com/example/SimpleClass.java new file mode 100644 index 00000000000..523b80f5fd2 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/src/com/example/SimpleClass.java @@ -0,0 +1,16 @@ +package com.example; + +public class SimpleClass { + + public String publicField = "publicField's value"; + + private String privateField = "privateField's value"; + + public String publicMethod() { + return "publicMethod's body"; + } + + private String privateMethod() { + return "privateMethod's body"; + } +} \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/FileFacadeKt.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/FileFacadeKt.class new file mode 100644 index 0000000000000000000000000000000000000000..1d3b87a06baac32893eaf6785e73afb0debddc2b GIT binary patch literal 802 zcmZuu%Wl&^6g@L`T{lfzHw4-|D5bpG4Neg&Ku8Ew$szDk z58(@V>{y_xOFjy52StRETJqe7&pG${%+Fune*k!hy9}FQI`ZYQ7>yI@AH<0~5TS_V zOT7rj;0?q?_=!+`|8;L5L+V-#8-1zYWa(ID`Xo>@5<_)&uRWt2>MU0M#|*dHY1a1# zQui_utK1h#rCR7XrM#2sPLU*ps*W$!tjsHx;bLhC93^6XC{$>qE^gAetbH%v?xoR* zixmdJdRD)qAc2HXq}ywO7wDXwZvVk?s#^cx7beAJ3BSW`}2=4&ObRWVd}QaJ+hid z515f$nYny_s2Lh>3#H?c4Admf<6a`4%`9cGpQn*zSZl{hc8XC?X72=H7)pm}k%iKT zG1R6SM{^3;CvWXP|57(r(*ifCawslRv{1tWRUA81HK}VJ`zw||Q>5#r(_jtqUtSI6 zzpTcTWmQ)HC)pQBF4o`?*o@kmlAKEA8qu ez-B<(*8?=r?1JUEj;$^ha=1BepTD literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass.class new file mode 100644 index 0000000000000000000000000000000000000000..9ddcd3086827a0ddc009f3ef24748c20907a3964 GIT binary patch literal 809 zcmb7>%}xR_6opR#L2(rP7sY=~bYVt_J6FclL`0*;jSEwtl47T0W;#UQ%B6AP1NczJ zTQD&?5##3e_TGEiucvSCFRuXhvBj{^418BSaQ|Kj_as(E-by8o6px}D7(=ncyWCY= zx82i5M>I_iX@+uJn6ogr7omCd^mkc>VS0O~_NBWpq15dIhK*Vfw%v{}jgU(nxm@eO za3cfC>w&4qO3~Er??g8@Kdw3^oM8t;$mTJEJVWU=)=hh2g35u@pBfvCU;P(B)e;mP z6ztg(m>fV*buitZnqioz-33NT?Vbq3TikGZc*^gl>06i$SsTE>?rf5w=7Y4^9%U_Z zICB)V1Vf=FwW!B_Lxh*Sp{TNW9&00g;pwi7B$W=oOeVO`?p30P3R1|}ODUGDqqJY6 zU709))n|-8QNplJG25%smwx6sT1(d9q-y*x6?vCbP5q@JFO#a-!77GL;@lzjIeO*% bHPRkR9u_<-qU>P_%N|y+dW}?s;RwYKTac_& literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass__MultifileClass1Kt.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/classes/com/com/example/MultifileClass__MultifileClass1Kt.class new file mode 100644 index 0000000000000000000000000000000000000000..b2c3fa2b48d3c3a55bfce7398c87f3013b2c9860 GIT binary patch literal 892 zcmZ`%O>fgc5PfSWj+>U&4dEkBC}|5cJ%Cfh4Fp0(NET_RD5{Whs5Z$qa^l#LcO9j- zp7rJ)Lr zgHY^-T*{-P%kj<|l>=jN`h3j2kVif5ZM!czv}!U`dP40d@kk`<#E-5p8H%;-#s%*{ zB|+4C%COmplb+WXs-5s4k{*wuSaB7^gqyKyreR1~rO|xh!mR8PY%DCmK%S__Y1Gkr z|LLhZcW+Bp+i~~A#x(}}StkslKs{$LYukqm>vOvy+L(u_u(8Up+8D%&nBJaHyvr4* zgBZiHNjJ~5v9tjL8)%TF%}Jn9oltj&p?-Fn&(m)>%Xz~#UCYV1MxAXGowHL7KAeAi zb^gh+DaXy&hC?$jx{sNbG$xY4oj+NI!j%C%8YqVHyEIb4Q25a}kU={XFE6ISklBs9 zf?=f*M537v+ah_-DTg6{5T{8;yry?U_dV4(oKbMfgc5PfSWj+>U&4dEkBC}|5cJ%B^S4Fp0(NET_RD5{Whs5Z$q+QhLVdmW{> zp7=jN`h3j2fQLQrZM!czv}!U`dP42T(MZJV#1F498H%;-#s%*{ z#WL(YW!P*)anI`u)sDFg6OV^sq_~n1;bx?oX%J9WX*6HBFe|$R8w(3CkSFSK8g{ha ze|oCU-P=m4?WlWV;~IngtP{vks^<)5ZTpa6eQq~I8}lF)HdYx{8-qv@)7uk@ce&zp z5Mwwt>E@X>mNsBu0}ZmYIguLG33VSc)Xz@ydHM}!Id9meYdIO$sI!ftb9So1hx3oG z&Occ;<+vH!aA*cbcZX?7W0Ekq^C!ztxH6ze1I18&mxf9Xg&&S(BHMv@c`*%!%x=^b z3@Z&8ie@@&i}*dK9ESWsl*S$Ln%)iF_f+F>M!~7mL^6Ml?CJqgpPLj}-9qEG%CoQ7So|_m z%)kR$Gq8az@-Ha+Iu$IoVEVY>Wxb0K-u8%S*EwBW4u+~C0ft}!PqJfoAo7DvC FzX4t7$}0c> literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/FileFacade.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/FileFacade.kt new file mode 100644 index 00000000000..0cb4c218900 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/FileFacade.kt @@ -0,0 +1,4 @@ +package com.example + +val propertyInFileFacade = "propertyInFileFacade's value" +fun functionInFileFacade() = "functionInFileFacade's body" \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass1.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass1.kt new file mode 100644 index 00000000000..3746db1e254 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass1.kt @@ -0,0 +1,7 @@ +@file:JvmName("MultifileClass") +@file:JvmMultifileClass + +package com.example + +val propertyInMultifileClass1 = "propertyInMultifileClass1's value" +fun functionInMultifileClass1() = "functionInMultifileClass1's body" \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass2.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass2.kt new file mode 100644 index 00000000000..8e5d45a3279 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testPackageFacadeClasses/src/com/example/MultifileClass2.kt @@ -0,0 +1,7 @@ +@file:JvmName("MultifileClass") +@file:JvmMultifileClass + +package com.example + +val propertyInMultifileClass2 = "propertyInMultifileClass2's value" +fun functionInMultifileClass2() = "functionInMultifileClass2's body" \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/classes/com/example/SimpleClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/classes/com/example/SimpleClass.class new file mode 100644 index 0000000000000000000000000000000000000000..f1e53aca7948aa3d1cbaacdc8ff8087bc889dfa7 GIT binary patch literal 1159 zcmZ`%T~8BH5IuLd-ENm!wrlw)prWWi6}AxHFeZvYODYmD#Du5Yc2h5Gcbna918^2L0wutNFff*Knw|n$wss6ipp-_H=H0k zT@2@2+-*8Kk__pqF$SwNAm5Jy;ng-$7(*%v1-D37BM>bfIoHX?8OBF~DP($u7DFuh zBtnK<=|7EaFl1^@w0lJ=Lw0HTx*&#yk{{IUBPS{cTzH|)J { log.debug("Deleting output file: $file") - file.forceDeleteRecursively() + file.deleteRecursivelyOrThrow() } } }