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
This commit is contained in:
committed by
nataliya.valtman
parent
05457c291d
commit
d2193f3873
Generated
+2
@@ -1,6 +1,8 @@
|
|||||||
<component name="ProjectDictionaryState">
|
<component name="ProjectDictionaryState">
|
||||||
<dictionary name="hungnv">
|
<dictionary name="hungnv">
|
||||||
<words>
|
<words>
|
||||||
|
<w>externalizers</w>
|
||||||
|
<w>granularities</w>
|
||||||
<w>multifile</w>
|
<w>multifile</w>
|
||||||
<w>shrinker</w>
|
<w>shrinker</w>
|
||||||
<w>snapshotter</w>
|
<w>snapshotter</w>
|
||||||
|
|||||||
@@ -35,33 +35,33 @@ fun File.isClassFile(): Boolean =
|
|||||||
*/
|
*/
|
||||||
fun File.cleanDirectoryContents() {
|
fun File.cleanDirectoryContents() {
|
||||||
when {
|
when {
|
||||||
isDirectory -> listFiles()!!.forEach { it.forceDeleteRecursively() }
|
isDirectory -> listFiles()!!.forEach { it.deleteRecursivelyOrThrow() }
|
||||||
isFile -> error("File.cleanDirectoryContents does not accept a regular file: $path")
|
isFile -> error("File.cleanDirectoryContents does not accept a regular file: $path")
|
||||||
else -> forceMkdirs()
|
else -> mkdirsOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Deletes this file or directory recursively (if it exists). */
|
/** Deletes this file or directory recursively (if it exists), throwing an exception if the deletion failed. */
|
||||||
fun File.forceDeleteRecursively() {
|
fun File.deleteRecursivelyOrThrow() {
|
||||||
if (!deleteRecursively()) {
|
if (!deleteRecursively()) {
|
||||||
throw IOException("Could not delete '$path'")
|
throw IOException("Could not delete '$path'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates this directory (if it does not yet exist).
|
* 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.
|
||||||
* If this is a regular file, this method will throw an exception.
|
|
||||||
*/
|
*/
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
fun File.forceMkdirs() {
|
fun File.mkdirsOrThrow() {
|
||||||
when {
|
when {
|
||||||
this.isDirectory -> { /* Do nothing */ }
|
isDirectory -> Unit
|
||||||
this.isFile -> error("File.forceMkdirs does not accept a regular file: $path")
|
isFile -> error("A regular file already exists at this path: $path")
|
||||||
else -> {
|
else -> {
|
||||||
// Note that if the directory already exists, mkdirs() will return `false`, but here we ensure that the directory does not exist
|
// Note that `mkdirs()` returns `false` if the directory already exists (even though we have checked that the directory did not
|
||||||
// before calling mkdirs(), so it's safe to check the returned result of mkdirs() below.
|
// exist earlier, it might just have been created by some other thread). Therefore, we need to check if the directory exists
|
||||||
if (!mkdirs()) {
|
// again when `mkdirs()` returns `false`.
|
||||||
|
if (!mkdirs() && !isDirectory) {
|
||||||
throw IOException("Could not create directory '$path'")
|
throw IOException("Could not create directory '$path'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ object ByteArrayExternalizer : DataExternalizer<ByteArray> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class GenericCollectionExternalizer<T, C : Collection<T>>(
|
abstract class GenericCollectionExternalizer<T, C : Collection<T>>(
|
||||||
private val elementExternalizer: DataExternalizer<T>,
|
private val elementExternalizer: DataExternalizer<T>,
|
||||||
private val newCollection: (size: Int) -> MutableCollection<T>
|
private val newCollection: (size: Int) -> MutableCollection<T>
|
||||||
) : DataExternalizer<C> {
|
) : DataExternalizer<C> {
|
||||||
@@ -364,6 +364,9 @@ open class GenericCollectionExternalizer<T, C : Collection<T>>(
|
|||||||
repeat(size) {
|
repeat(size) {
|
||||||
collection.add(elementExternalizer.read(input))
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return collection as C
|
return collection as C
|
||||||
}
|
}
|
||||||
@@ -375,12 +378,13 @@ class ListExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
|||||||
class SetExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
class SetExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
||||||
GenericCollectionExternalizer<T, Set<T>>(elementExternalizer, { size -> LinkedHashSet(size) })
|
GenericCollectionExternalizer<T, Set<T>>(elementExternalizer, { size -> LinkedHashSet(size) })
|
||||||
|
|
||||||
class LinkedHashMapExternalizer<K, V>(
|
open class MapExternalizer<K, V, M : Map<K, V>>(
|
||||||
private val keyExternalizer: DataExternalizer<K>,
|
private val keyExternalizer: DataExternalizer<K>,
|
||||||
private val valueExternalizer: DataExternalizer<V>
|
private val valueExternalizer: DataExternalizer<V>,
|
||||||
) : DataExternalizer<LinkedHashMap<K, V>> {
|
private val newMap: (size: Int) -> MutableMap<K, V> = { size -> LinkedHashMap(size) }
|
||||||
|
) : DataExternalizer<M> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, map: LinkedHashMap<K, V>) {
|
override fun save(output: DataOutput, map: M) {
|
||||||
output.writeInt(map.size)
|
output.writeInt(map.size)
|
||||||
for ((key, value) in map) {
|
for ((key, value) in map) {
|
||||||
keyExternalizer.save(output, key)
|
keyExternalizer.save(output, key)
|
||||||
@@ -388,14 +392,20 @@ class LinkedHashMapExternalizer<K, V>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): LinkedHashMap<K, V> {
|
override fun read(input: DataInput): M {
|
||||||
val size = input.readInt()
|
val size = input.readInt()
|
||||||
val map = LinkedHashMap<K, V>(size)
|
val map = newMap(size)
|
||||||
repeat(size) {
|
repeat(size) {
|
||||||
val key = keyExternalizer.read(input)
|
val key = keyExternalizer.read(input)
|
||||||
val value = valueExternalizer.read(input)
|
val value = valueExternalizer.read(input)
|
||||||
map[key] = value
|
map[key] = value
|
||||||
}
|
}
|
||||||
return map
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return map as M
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LinkedHashMapExternalizer<K, V>(
|
||||||
|
keyExternalizer: DataExternalizer<K>,
|
||||||
|
valueExternalizer: DataExternalizer<V>
|
||||||
|
) : MapExternalizer<K, V, LinkedHashMap<K, V>>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) })
|
||||||
|
|||||||
+1
-1
@@ -216,7 +216,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
}
|
}
|
||||||
it.isFile -> {
|
it.isFile -> {
|
||||||
reporter.reportVerbose { " Deleting file '${it.path}'" }
|
reporter.reportVerbose { " Deleting file '${it.path}'" }
|
||||||
it.forceDeleteRecursively()
|
it.deleteRecursivelyOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -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.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailableForJSCompiler
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailableForJSCompiler
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.*
|
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.ExpectActualTracker
|
||||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||||
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
|
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
|
||||||
@@ -219,8 +221,8 @@ class IncrementalJvmCompilerRunner(
|
|||||||
|
|
||||||
// Used by `calculateSourcesToCompileImpl` and `performWorkAfterSuccessfulCompilation` methods below.
|
// Used by `calculateSourcesToCompileImpl` and `performWorkAfterSuccessfulCompilation` methods below.
|
||||||
// Thread safety: There is no concurrent access to these variables.
|
// Thread safety: There is no concurrent access to these variables.
|
||||||
private var currentClasspathSnapshot: List<ClassSnapshotWithHash>? = null
|
private var currentClasspathSnapshot: List<AccessibleClassSnapshot>? = null
|
||||||
private var shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>? = null
|
private var shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>? = null
|
||||||
|
|
||||||
private fun calculateSourcesToCompileImpl(
|
private fun calculateSourcesToCompileImpl(
|
||||||
caches: IncrementalJvmCachesManager,
|
caches: IncrementalJvmCachesManager,
|
||||||
@@ -245,9 +247,15 @@ class IncrementalJvmCompilerRunner(
|
|||||||
}
|
}
|
||||||
check(shrunkCurrentClasspathAgainstPreviousLookups == null)
|
check(shrunkCurrentClasspathAgainstPreviousLookups == null)
|
||||||
shrunkCurrentClasspathAgainstPreviousLookups = reporter.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT) {
|
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!!,
|
shrunkCurrentClasspathAgainstPreviousLookups!!,
|
||||||
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
||||||
reporter
|
reporter
|
||||||
|
|||||||
+6
@@ -49,6 +49,12 @@ class ChangeSet(
|
|||||||
|
|
||||||
fun addChangedClassMember(className: ClassId, memberName: String) = addChangedClassMembers(className, listOf(memberName))
|
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<String>) {
|
fun addChangedTopLevelMembers(packageName: FqName, topLevelMembers: Collection<String>) {
|
||||||
if (topLevelMembers.isNotEmpty()) {
|
if (topLevelMembers.isNotEmpty()) {
|
||||||
changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(topLevelMembers)
|
changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(topLevelMembers)
|
||||||
|
|||||||
+83
-56
@@ -30,12 +30,12 @@ object ClasspathChangesComputer {
|
|||||||
* NOTE: The original classpath may contain duplicate classes, but the shrunk classpath must not contain duplicate classes.
|
* NOTE: The original classpath may contain duplicate classes, but the shrunk classpath must not contain duplicate classes.
|
||||||
*/
|
*/
|
||||||
fun computeChangedAndImpactedSet(
|
fun computeChangedAndImpactedSet(
|
||||||
shrunkCurrentClasspathSnapshot: List<ClassSnapshotWithHash>,
|
shrunkCurrentClasspathSnapshot: List<AccessibleClassSnapshot>,
|
||||||
shrunkPreviousClasspathSnapshotFile: File,
|
shrunkPreviousClasspathSnapshotFile: File,
|
||||||
metrics: BuildMetricsReporter
|
metrics: BuildMetricsReporter
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val shrunkPreviousClasspathSnapshot = metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT) {
|
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) {
|
return metrics.measure(BuildTime.COMPUTE_CHANGED_AND_IMPACTED_SET) {
|
||||||
computeChangedAndImpactedSet(shrunkCurrentClasspathSnapshot, shrunkPreviousClasspathSnapshot, metrics)
|
computeChangedAndImpactedSet(shrunkCurrentClasspathSnapshot, shrunkPreviousClasspathSnapshot, metrics)
|
||||||
@@ -48,22 +48,26 @@ object ClasspathChangesComputer {
|
|||||||
* NOTE: Each list of classes must not contain duplicates.
|
* NOTE: Each list of classes must not contain duplicates.
|
||||||
*/
|
*/
|
||||||
fun computeChangedAndImpactedSet(
|
fun computeChangedAndImpactedSet(
|
||||||
currentClassSnapshots: List<ClassSnapshotWithHash>,
|
currentClassSnapshots: List<AccessibleClassSnapshot>,
|
||||||
previousClassSnapshots: List<ClassSnapshotWithHash>,
|
previousClassSnapshots: List<AccessibleClassSnapshot>,
|
||||||
metrics: BuildMetricsReporter
|
metrics: BuildMetricsReporter
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val currentClasses: Map<ClassId, ClassSnapshotWithHash> = currentClassSnapshots.associateBy { it.classSnapshot.getClassId() }
|
val currentClasses: Map<ClassId, AccessibleClassSnapshot> = currentClassSnapshots.associateBy { it.classId }
|
||||||
val previousClasses: Map<ClassId, ClassSnapshotWithHash> = previousClassSnapshots.associateBy { it.classSnapshot.getClassId() }
|
val previousClasses: Map<ClassId, AccessibleClassSnapshot> = previousClassSnapshots.associateBy { it.classId }
|
||||||
|
|
||||||
val changedCurrentClasses: List<ClassSnapshot> = currentClasses.filter { (classId, currentClass) ->
|
val changedCurrentClasses: List<AccessibleClassSnapshot> = currentClasses.mapNotNull { (classId, currentClass) ->
|
||||||
val previousClass = previousClasses[classId]
|
val previousClass = previousClasses[classId]
|
||||||
previousClass == null || currentClass.hash != previousClass.hash
|
if (previousClass == null || currentClass.classAbiHash != previousClass.classAbiHash) {
|
||||||
}.map { it.value.classSnapshot }
|
currentClass
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
val changedPreviousClasses: List<ClassSnapshot> = previousClasses.filter { (classId, previousClass) ->
|
val changedPreviousClasses: List<AccessibleClassSnapshot> = previousClasses.mapNotNull { (classId, previousClass) ->
|
||||||
val currentClass = currentClasses[classId]
|
val currentClass = currentClasses[classId]
|
||||||
currentClass == null || currentClass.hash != previousClass.hash
|
if (currentClass == null || currentClass.classAbiHash != previousClass.classAbiHash) {
|
||||||
}.map { it.value.classSnapshot }
|
previousClass
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
val classChanges = metrics.measure(BuildTime.COMPUTE_CLASS_CHANGES) {
|
val classChanges = metrics.measure(BuildTime.COMPUTE_CLASS_CHANGES) {
|
||||||
computeClassChanges(changedCurrentClasses, changedPreviousClasses, metrics)
|
computeClassChanges(changedCurrentClasses, changedPreviousClasses, metrics)
|
||||||
@@ -74,7 +78,7 @@ object ClasspathChangesComputer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return metrics.measure(BuildTime.COMPUTE_IMPACTED_SET) {
|
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.
|
* NOTE: Each list of classes must not contain duplicates.
|
||||||
*/
|
*/
|
||||||
fun computeClassChanges(
|
private fun computeClassChanges(
|
||||||
currentClassSnapshots: List<ClassSnapshot>,
|
currentClassSnapshots: List<AccessibleClassSnapshot>,
|
||||||
previousClassSnapshots: List<ClassSnapshot>,
|
previousClassSnapshots: List<AccessibleClassSnapshot>,
|
||||||
metrics: BuildMetricsReporter
|
metrics: BuildMetricsReporter
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val isKotlinClass: (ClassSnapshot) -> Boolean = {
|
val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition { it is KotlinClassSnapshot }
|
||||||
when (it) {
|
val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition { it is KotlinClassSnapshot }
|
||||||
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)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
|
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
|
||||||
@@ -121,6 +118,36 @@ object ClasspathChangesComputer {
|
|||||||
private fun computeKotlinClassChanges(
|
private fun computeKotlinClassChanges(
|
||||||
currentClassSnapshots: List<KotlinClassSnapshot>,
|
currentClassSnapshots: List<KotlinClassSnapshot>,
|
||||||
previousClassSnapshots: List<KotlinClassSnapshot>
|
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||||
|
): 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<KotlinClassSnapshot>,
|
||||||
|
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||||
|
): 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<KotlinClassSnapshot>,
|
||||||
|
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val workingDir =
|
val workingDir =
|
||||||
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
|
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
|
||||||
@@ -135,11 +162,11 @@ object ClasspathChangesComputer {
|
|||||||
val unusedChangesCollector = ChangesCollector()
|
val unusedChangesCollector = ChangesCollector()
|
||||||
previousClassSnapshots.forEach {
|
previousClassSnapshots.forEach {
|
||||||
incrementalJvmCache.saveClassToCache(
|
incrementalJvmCache.saveClassToCache(
|
||||||
kotlinClassInfo = it.classInfo,
|
kotlinClassInfo = it.classMemberLevelSnapshot!!,
|
||||||
sourceFiles = null,
|
sourceFiles = null,
|
||||||
changesCollector = unusedChangesCollector
|
changesCollector = unusedChangesCollector
|
||||||
)
|
)
|
||||||
incrementalJvmCache.markDirty(it.classInfo.className)
|
incrementalJvmCache.markDirty(it.classMemberLevelSnapshot!!.className)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2:
|
// Step 2:
|
||||||
@@ -152,7 +179,7 @@ object ClasspathChangesComputer {
|
|||||||
val changesCollector = ChangesCollector()
|
val changesCollector = ChangesCollector()
|
||||||
currentClassSnapshots.forEach {
|
currentClassSnapshots.forEach {
|
||||||
incrementalJvmCache.saveClassToCache(
|
incrementalJvmCache.saveClassToCache(
|
||||||
kotlinClassInfo = it.classInfo,
|
kotlinClassInfo = it.classMemberLevelSnapshot!!,
|
||||||
sourceFiles = null,
|
sourceFiles = null,
|
||||||
changesCollector = changesCollector
|
changesCollector = changesCollector
|
||||||
)
|
)
|
||||||
@@ -172,7 +199,10 @@ object ClasspathChangesComputer {
|
|||||||
return dirtyData.normalize(currentClassSnapshots, previousClassSnapshots)
|
return dirtyData.normalize(currentClassSnapshots, previousClassSnapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DirtyData.normalize(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
private fun DirtyData.normalize(
|
||||||
|
currentClassSnapshots: List<AccessibleClassSnapshot>,
|
||||||
|
previousClassSnapshots: List<AccessibleClassSnapshot>
|
||||||
|
): ChangeSet {
|
||||||
val changedLookupSymbols =
|
val changedLookupSymbols =
|
||||||
dirtyLookupSymbols.filterLookupSymbols(currentClassSnapshots).toSet() +
|
dirtyLookupSymbols.filterLookupSymbols(currentClassSnapshots).toSet() +
|
||||||
dirtyLookupSymbols.filterLookupSymbols(previousClassSnapshots)
|
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.
|
* The returned set is also a [ChangeSet], which includes the given changes plus the impacted ones.
|
||||||
*/
|
*/
|
||||||
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<AccessibleClassSnapshot>): ChangeSet {
|
||||||
val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots)
|
val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots)
|
||||||
val impactedClassesResolver = { classId: ClassId -> classIdToSubclasses[classId] ?: emptySet() }
|
val impactedClassesResolver = { classId: ClassId -> classIdToSubclasses[classId] ?: emptySet() }
|
||||||
|
|
||||||
@@ -234,14 +264,14 @@ internal object ImpactAnalysis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getClassIdToSubclassesMap(classSnapshots: List<ClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
private fun getClassIdToSubclassesMap(classSnapshots: List<AccessibleClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
||||||
val classIds: Set<ClassId> = classSnapshots.map { it.getClassId() }.toSet() // Use Set for presence check
|
val classIds: Set<ClassId> = classSnapshots.map { it.classId }.toSet() // Use Set for presence check
|
||||||
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||||
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
||||||
|
|
||||||
val classIdToSubclasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
|
val classIdToSubclasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
|
||||||
classSnapshots.forEach { classSnapshot ->
|
classSnapshots.forEach { classSnapshot ->
|
||||||
val classId = classSnapshot.getClassId()
|
val classId = classSnapshot.classId
|
||||||
classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype ->
|
classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype ->
|
||||||
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
||||||
if (supertype in classIds) {
|
if (supertype in classIds) {
|
||||||
@@ -257,39 +287,36 @@ internal object ImpactAnalysis {
|
|||||||
* impacted classes.
|
* impacted classes.
|
||||||
*/
|
*/
|
||||||
fun findImpactedClassesInclusive(classIds: Set<ClassId>, impactedClassesResolver: (ClassId) -> Set<ClassId>): Set<ClassId> {
|
fun findImpactedClassesInclusive(classIds: Set<ClassId>, impactedClassesResolver: (ClassId) -> Set<ClassId>): Set<ClassId> {
|
||||||
val visitedClasses = mutableSetOf<ClassId>()
|
// Standard Breadth-First Search
|
||||||
val toVisitClasses = classIds.toMutableSet()
|
val visitedAndToVisitClasses = classIds.toMutableSet()
|
||||||
while (toVisitClasses.isNotEmpty()) {
|
val classesToVisit = ArrayDeque(classIds)
|
||||||
val nextToVisit = mutableSetOf<ClassId>()
|
|
||||||
toVisitClasses.forEach {
|
|
||||||
nextToVisit.addAll(impactedClassesResolver.invoke(it))
|
|
||||||
}
|
|
||||||
visitedClasses.addAll(toVisitClasses)
|
|
||||||
toVisitClasses.clear()
|
|
||||||
toVisitClasses.addAll(nextToVisit - visitedClasses)
|
|
||||||
}
|
|
||||||
return visitedClasses
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ClassSnapshot.getClassId(): ClassId {
|
while (classesToVisit.isNotEmpty()) {
|
||||||
return when (this) {
|
val classToVisit = classesToVisit.removeFirst()
|
||||||
is KotlinClassSnapshot -> classInfo.classId
|
val nextClassesToVisit = impactedClassesResolver.invoke(classToVisit) - visitedAndToVisitClasses
|
||||||
is JavaClassSnapshot -> classId
|
visitedAndToVisitClasses.addAll(nextClassesToVisit)
|
||||||
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
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
|
* @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.
|
* 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<ClassId> {
|
internal fun AccessibleClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
||||||
return when (this) {
|
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 JavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||||
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-65
@@ -6,8 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
import org.jetbrains.kotlin.incremental.md5
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
|
||||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
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
|
* Maps (Unix-style) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory
|
||||||
* or jar).
|
* or jar).
|
||||||
*/
|
*/
|
||||||
val classSnapshots: LinkedHashMap<String, ClassSnapshotWithHash>
|
val classSnapshots: LinkedHashMap<String, ClassSnapshot>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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,
|
* `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.
|
* 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]. */
|
/** [ClassSnapshot] of an accessible class. See [InaccessibleClassSnapshot] for info on the accessibility of a class. */
|
||||||
val withHash: ClassSnapshotWithHash by lazy {
|
sealed class AccessibleClassSnapshot : ClassSnapshot() {
|
||||||
ClassSnapshotWithHash(this, ClassSnapshotExternalizer.toByteArray(this).md5())
|
abstract val classId: ClassId
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Contains a [ClassSnapshot] and its hash. */
|
/** The hash of the class's ABI. */
|
||||||
class ClassSnapshotWithHash(val classSnapshot: ClassSnapshot, val hash: Long) {
|
abstract val classAbiHash: Long
|
||||||
|
|
||||||
override fun toString() = classSnapshot.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [ClassSnapshot] of a Kotlin class. */
|
|
||||||
class KotlinClassSnapshot(
|
|
||||||
val classInfo: KotlinClassInfo,
|
|
||||||
val supertypes: List<JvmClassName>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<PackageMember>?
|
|
||||||
) : 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<JvmClassName>,
|
|
||||||
|
|
||||||
/** [AbiSnapshot] of the class excluding its fields and methods. */
|
|
||||||
val classAbiExcludingMembers: AbiSnapshot,
|
|
||||||
|
|
||||||
/** [AbiSnapshot]s of the class's fields. */
|
|
||||||
val fieldsAbi: List<AbiSnapshot>,
|
|
||||||
|
|
||||||
/** [AbiSnapshot]s of the class's methods. */
|
|
||||||
val methodsAbi: List<AbiSnapshot>
|
|
||||||
|
|
||||||
) : ClassSnapshot() {
|
|
||||||
|
|
||||||
val className by lazy {
|
|
||||||
JvmClassName.byClassId(classId).also {
|
|
||||||
check(it == JvmClassName.byInternalName(classAbiExcludingMembers.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = classId.toString()
|
override fun toString() = classId.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The ABI snapshot of a Java element (e.g., class, field, or method). */
|
/** [ClassSnapshot] of a Kotlin class. */
|
||||||
open class AbiSnapshot(
|
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<JvmClassName>
|
||||||
|
) : 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<JvmClassName>
|
||||||
|
) : 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>,
|
||||||
|
|
||||||
|
/** [JavaElementSnapshot]s of the class's methods. */
|
||||||
|
val methodsAbi: List<JavaElementSnapshot>
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 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. */
|
/** The name of the Java element. It is part of the Java element's ABI. */
|
||||||
val name: String,
|
val name: String,
|
||||||
@@ -109,15 +122,16 @@ open class AbiSnapshot(
|
|||||||
val abiHash: Long
|
val abiHash: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
/** TEST-ONLY: An [AbiSnapshot] that is used for testing only and must not be used in production code. */
|
/** TEST-ONLY: A [JavaElementSnapshot] that is used for testing only and must not be used in production code. */
|
||||||
class AbiSnapshotForTests(
|
class JavaElementSnapshotForTests(
|
||||||
name: String,
|
name: String,
|
||||||
abiHash: Long,
|
abiHash: Long,
|
||||||
|
|
||||||
/** The Java element's ABI, captured in a [String]. */
|
/** 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.
|
* [ClassSnapshot] of an inaccessible class.
|
||||||
@@ -126,3 +140,19 @@ class AbiSnapshotForTests(
|
|||||||
* will not require recompilation of other source files.
|
* will not require recompilation of other source files.
|
||||||
*/
|
*/
|
||||||
object InaccessibleClassSnapshot : ClassSnapshot()
|
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
|
||||||
|
}
|
||||||
|
|||||||
+133
-74
@@ -48,76 +48,119 @@ object CachedClasspathSnapshotSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class DataExternalizerForSealedClass<T>(
|
||||||
|
val baseClass: Class<T>,
|
||||||
|
val inheritorClasses: List<Class<out T>>,
|
||||||
|
val inheritorExternalizers: List<DataExternalizer<*>>
|
||||||
|
) : DataExternalizer<T> {
|
||||||
|
|
||||||
|
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<T>).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<ClasspathEntrySnapshot> {
|
object ClasspathEntrySnapshotExternalizer : DataExternalizer<ClasspathEntrySnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
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 {
|
override fun read(input: DataInput): ClasspathEntrySnapshot {
|
||||||
return ClasspathEntrySnapshot(
|
return ClasspathEntrySnapshot(
|
||||||
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).read(input)
|
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotExternalizer).read(input)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ClassSnapshotExternalizer : DataExternalizer<ClassSnapshot> {
|
object ClassSnapshotExternalizer : DataExternalizerForSealedClass<ClassSnapshot>(
|
||||||
|
baseClass = ClassSnapshot::class.java,
|
||||||
|
inheritorClasses = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java),
|
||||||
|
inheritorExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer)
|
||||||
|
)
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: ClassSnapshot) {
|
object AccessibleClassSnapshotExternalizer : DataExternalizerForSealedClass<AccessibleClassSnapshot>(
|
||||||
when (snapshot) {
|
baseClass = AccessibleClassSnapshot::class.java,
|
||||||
is KotlinClassSnapshot -> {
|
inheritorClasses = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java),
|
||||||
output.writeString(KotlinClassSnapshot::class.java.name)
|
inheritorExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(input: DataInput): ClassSnapshot {
|
object KotlinClassSnapshotExternalizer : DataExternalizerForSealedClass<KotlinClassSnapshot>(
|
||||||
return when (val className = input.readString()) {
|
baseClass = KotlinClassSnapshot::class.java,
|
||||||
KotlinClassSnapshot::class.java.name -> KotlinClassSnapshotExternalizer.read(input)
|
inheritorClasses = listOf(
|
||||||
JavaClassSnapshot::class.java.name -> JavaClassSnapshotExternalizer.read(input)
|
RegularKotlinClassSnapshot::class.java,
|
||||||
InaccessibleClassSnapshot::class.java.name -> InaccessibleClassSnapshotExternalizer.read(input)
|
PackageFacadeKotlinClassSnapshot::class.java,
|
||||||
else -> error("Unrecognized class name: $className")
|
MultifileClassKotlinClassSnapshot::class.java
|
||||||
}
|
),
|
||||||
}
|
inheritorExternalizers = listOf(
|
||||||
}
|
RegularKotlinClassSnapshotExternalizer,
|
||||||
|
PackageFacadeKotlinClassSnapshotExternalizer,
|
||||||
|
MultifileClassKotlinClassSnapshotExternalizer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
object ClassSnapshotWithHashExternalizer : DataExternalizer<ClassSnapshotWithHash> {
|
object RegularKotlinClassSnapshotExternalizer : DataExternalizer<RegularKotlinClassSnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: ClassSnapshotWithHash) {
|
override fun save(output: DataOutput, snapshot: RegularKotlinClassSnapshot) {
|
||||||
ClassSnapshotExternalizer.save(output, snapshot.classSnapshot)
|
ClassIdExternalizer.save(output, snapshot.classId)
|
||||||
LongExternalizer.save(output, snapshot.hash)
|
LongExternalizer.save(output, snapshot.classAbiHash)
|
||||||
}
|
NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot)
|
||||||
|
|
||||||
override fun read(input: DataInput): ClassSnapshotWithHash {
|
|
||||||
return ClassSnapshotWithHash(
|
|
||||||
classSnapshot = ClassSnapshotExternalizer.read(input),
|
|
||||||
hash = LongExternalizer.read(input)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> {
|
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) {
|
|
||||||
KotlinClassInfoExternalizer.save(output, snapshot.classInfo)
|
|
||||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
||||||
NullableValueExternalizer(ListExternalizer(PackageMemberExternalizer)).save(output, snapshot.packageMembers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): KotlinClassSnapshot {
|
override fun read(input: DataInput): RegularKotlinClassSnapshot {
|
||||||
return KotlinClassSnapshot(
|
return RegularKotlinClassSnapshot(
|
||||||
classInfo = KotlinClassInfoExternalizer.read(input),
|
classId = ClassIdExternalizer.read(input),
|
||||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
classAbiHash = LongExternalizer.read(input),
|
||||||
packageMembers = NullableValueExternalizer(ListExternalizer(PackageMemberExternalizer)).read(input)
|
classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input),
|
||||||
|
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object PackageFacadeKotlinClassSnapshotExternalizer : DataExternalizer<PackageFacadeKotlinClassSnapshot> {
|
||||||
|
|
||||||
|
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<MultifileClassKotlinClassSnapshot> {
|
||||||
|
|
||||||
|
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<KotlinClassInfo> {
|
|||||||
|
|
||||||
override fun save(output: DataOutput, info: KotlinClassInfo) {
|
override fun save(output: DataOutput, info: KotlinClassInfo) {
|
||||||
ClassIdExternalizer.save(output, info.classId)
|
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.classHeaderData.toList())
|
||||||
ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList())
|
ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList())
|
||||||
NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName)
|
NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName)
|
||||||
@@ -137,7 +180,7 @@ object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo> {
|
|||||||
override fun read(input: DataInput): KotlinClassInfo {
|
override fun read(input: DataInput): KotlinClassInfo {
|
||||||
return KotlinClassInfo(
|
return KotlinClassInfo(
|
||||||
classId = ClassIdExternalizer.read(input),
|
classId = ClassIdExternalizer.read(input),
|
||||||
classKind = KotlinClassHeader.Kind.getById(input.readInt()),
|
classKind = KotlinClassHeader.Kind.getById(IntExternalizer.read(input)),
|
||||||
classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||||
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||||
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
|
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
|
||||||
@@ -147,17 +190,15 @@ object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PackageMemberExternalizer : DataExternalizer<PackageMember> {
|
object PackageMemberSetExternalizer : DataExternalizer<PackageMemberSet> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, packageMember: PackageMember) {
|
override fun save(output: DataOutput, set: PackageMemberSet) {
|
||||||
FqNameExternalizer.save(output, packageMember.packageFqName)
|
MapExternalizer(FqNameExternalizer, SetExternalizer(StringExternalizer)).save(output, set.packageToMembersMap)
|
||||||
StringExternalizer.save(output, packageMember.memberName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): PackageMember {
|
override fun read(input: DataInput): PackageMemberSet {
|
||||||
return PackageMember(
|
return PackageMemberSet(
|
||||||
packageFqName = FqNameExternalizer.read(input),
|
packageToMembersMap = MapExternalizer(FqNameExternalizer, SetExternalizer(StringExternalizer)).read(input)
|
||||||
memberName = StringExternalizer.read(input)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,32 +207,50 @@ object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
|||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: JavaClassSnapshot) {
|
override fun save(output: DataOutput, snapshot: JavaClassSnapshot) {
|
||||||
ClassIdExternalizer.save(output, snapshot.classId)
|
ClassIdExternalizer.save(output, snapshot.classId)
|
||||||
|
LongExternalizer.save(output, snapshot.classAbiHash)
|
||||||
|
NullableValueExternalizer(JavaClassMemberLevelSnapshotExternalizer).save(output, snapshot.classMemberLevelSnapshot)
|
||||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
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 {
|
override fun read(input: DataInput): JavaClassSnapshot {
|
||||||
return JavaClassSnapshot(
|
return JavaClassSnapshot(
|
||||||
classId = ClassIdExternalizer.read(input),
|
classId = ClassIdExternalizer.read(input),
|
||||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
classAbiHash = LongExternalizer.read(input),
|
||||||
classAbiExcludingMembers = AbiSnapshotExternalizer.read(input),
|
classMemberLevelSnapshot = NullableValueExternalizer(JavaClassMemberLevelSnapshotExternalizer).read(input),
|
||||||
fieldsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input),
|
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input)
|
||||||
methodsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object AbiSnapshotExternalizer : DataExternalizer<AbiSnapshot> {
|
object JavaClassMemberLevelSnapshotExternalizer : DataExternalizer<JavaClassMemberLevelSnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, value: AbiSnapshot) {
|
override fun save(output: DataOutput, snapshot: JavaClassMemberLevelSnapshot) {
|
||||||
output.writeString(value.name)
|
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<JavaElementSnapshot> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, value: JavaElementSnapshot) {
|
||||||
|
StringExternalizer.save(output, value.name)
|
||||||
LongExternalizer.save(output, value.abiHash)
|
LongExternalizer.save(output, value.abiHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): AbiSnapshot {
|
override fun read(input: DataInput): JavaElementSnapshot {
|
||||||
return AbiSnapshot(name = input.readString(), abiHash = LongExternalizer.read(input))
|
return JavaElementSnapshot(
|
||||||
|
name = StringExternalizer.read(input),
|
||||||
|
abiHash = LongExternalizer.read(input)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+69
-57
@@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
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
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.NoChanges
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.NoChanges
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.ToBeComputedByIncrementalCompiler
|
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.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun
|
||||||
import org.jetbrains.kotlin.incremental.LookupStorage
|
import org.jetbrains.kotlin.incremental.LookupStorage
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
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.ListExternalizer
|
||||||
import org.jetbrains.kotlin.incremental.storage.LookupSymbolKey
|
import org.jetbrains.kotlin.incremental.storage.LookupSymbolKey
|
||||||
import org.jetbrains.kotlin.incremental.storage.loadFromFile
|
import org.jetbrains.kotlin.incremental.storage.loadFromFile
|
||||||
import org.jetbrains.kotlin.incremental.storage.saveToFile
|
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.name.ClassId
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
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].
|
* Shrinks the given classes by retaining only classes that are referenced by the lookup symbols stored in the given [LookupStorage].
|
||||||
*/
|
*/
|
||||||
fun shrink(
|
fun shrinkClasspath(
|
||||||
allClasses: List<ClassSnapshotWithHash>,
|
allClasses: List<AccessibleClassSnapshot>,
|
||||||
lookupStorage: LookupStorage,
|
lookupStorage: LookupStorage,
|
||||||
metrics: BuildMetricsReporter = DoNothingBuildMetricsReporter
|
metrics: MetricsReporter = MetricsReporter()
|
||||||
): List<ClassSnapshotWithHash> {
|
): List<AccessibleClassSnapshot> {
|
||||||
val lookupSymbols = metrics.measure(BuildTime.GET_LOOKUP_SYMBOLS) {
|
val lookupSymbols = metrics.getLookupSymbols {
|
||||||
lookupStorage.lookupSymbols
|
lookupStorage.lookupSymbols
|
||||||
.map { LookupSymbol(it.name, it.scope) }
|
.map { LookupSymbol(name = it.name, scope = it.scope) }
|
||||||
.filterLookupSymbols(allClasses.map { it.classSnapshot })
|
.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. */
|
/** Shrinks the given classes by retaining only classes that are referenced by the given lookup symbols. */
|
||||||
fun shrink(
|
fun shrinkClasses(
|
||||||
allClasses: List<ClassSnapshotWithHash>,
|
allClasses: List<AccessibleClassSnapshot>,
|
||||||
lookupSymbols: List<ProgramSymbol>,
|
lookupSymbols: List<ProgramSymbol>,
|
||||||
metrics: BuildMetricsReporter = DoNothingBuildMetricsReporter
|
metrics: MetricsReporter = MetricsReporter()
|
||||||
): List<ClassSnapshotWithHash> {
|
): List<AccessibleClassSnapshot> {
|
||||||
val referencedClasses = metrics.measure(BuildTime.FIND_REFERENCED_CLASSES) {
|
val referencedClasses = metrics.findReferencedClasses {
|
||||||
findReferencedClasses(allClasses, lookupSymbols)
|
findReferencedClasses(allClasses, lookupSymbols)
|
||||||
}
|
}
|
||||||
return metrics.measure(BuildTime.FIND_TRANSITIVELY_REFERENCED_CLASSES) {
|
return metrics.findTransitivelyReferencedClasses {
|
||||||
findTransitivelyReferencedClasses(allClasses, referencedClasses)
|
findTransitivelyReferencedClasses(allClasses, referencedClasses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,12 +60,12 @@ object ClasspathSnapshotShrinker {
|
|||||||
/**
|
/**
|
||||||
* Finds classes that are referenced by the given lookup symbols.
|
* 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(
|
private fun findReferencedClasses(
|
||||||
allClasses: List<ClassSnapshotWithHash>,
|
allClasses: List<AccessibleClassSnapshot>,
|
||||||
lookupSymbols: List<ProgramSymbol>
|
lookupSymbols: List<ProgramSymbol>
|
||||||
): List<ClassSnapshotWithHash> {
|
): List<AccessibleClassSnapshot> {
|
||||||
val lookedUpClassIds: Set<ClassId> = lookupSymbols.mapNotNullTo(mutableSetOf()) {
|
val lookedUpClassIds: Set<ClassId> = lookupSymbols.mapNotNullTo(mutableSetOf()) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is ClassSymbol -> it.classId
|
is ClassSymbol -> it.classId
|
||||||
@@ -70,18 +73,14 @@ object ClasspathSnapshotShrinker {
|
|||||||
is PackageMember -> null
|
is PackageMember -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val lookedUpPackageMembers: Set<PackageMember> = lookupSymbols.filterIsInstanceTo(mutableSetOf())
|
val lookedUpPackageMembers: PackageMemberSet =
|
||||||
|
lookupSymbols.filterIsInstanceTo<PackageMember, MutableSet<PackageMember>>(mutableSetOf()).compact()
|
||||||
|
|
||||||
return allClasses.filter {
|
return allClasses.filter {
|
||||||
val isPackageFacade =
|
when (it) {
|
||||||
it.classSnapshot is KotlinClassSnapshot && it.classSnapshot.classInfo.classKind != KotlinClassHeader.Kind.CLASS
|
is RegularKotlinClassSnapshot, is JavaClassSnapshot -> it.classId in lookedUpClassIds
|
||||||
if (isPackageFacade) {
|
is PackageFacadeKotlinClassSnapshot -> it.packageMembers.containsElementsIn(lookedUpPackageMembers)
|
||||||
// If packageMembers == null (e.g., if classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS -- see
|
is MultifileClassKotlinClassSnapshot -> it.constants.containsElementsIn(lookedUpPackageMembers)
|
||||||
// `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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,10 +92,10 @@ object ClasspathSnapshotShrinker {
|
|||||||
* The returned list includes the given referenced classes plus the transitively referenced ones.
|
* The returned list includes the given referenced classes plus the transitively referenced ones.
|
||||||
*/
|
*/
|
||||||
private fun findTransitivelyReferencedClasses(
|
private fun findTransitivelyReferencedClasses(
|
||||||
allClasses: List<ClassSnapshotWithHash>,
|
allClasses: List<AccessibleClassSnapshot>,
|
||||||
referencedClasses: List<ClassSnapshotWithHash>
|
referencedClasses: List<AccessibleClassSnapshot>
|
||||||
): List<ClassSnapshotWithHash> {
|
): List<AccessibleClassSnapshot> {
|
||||||
val classIdToClassSnapshot = allClasses.associateBy { it.classSnapshot.getClassId() }
|
val classIdToClassSnapshot = allClasses.associateBy { it.classId }
|
||||||
val classIds: Set<ClassId> = classIdToClassSnapshot.keys // Use Set for presence check
|
val classIds: Set<ClassId> = classIdToClassSnapshot.keys // Use Set for presence check
|
||||||
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||||
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
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")
|
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
||||||
@Suppress("SimpleRedundantLet")
|
@Suppress("SimpleRedundantLet")
|
||||||
classIdToClassSnapshot[classId]?.let {
|
classIdToClassSnapshot[classId]?.let {
|
||||||
it.classSnapshot.getSupertypes(classNameToClassIdResolver).filter { supertype -> supertype in classIds }.toSet()
|
it.getSupertypes(classNameToClassIdResolver).filterTo(mutableSetOf()) { supertype -> supertype in classIds }
|
||||||
} ?: emptySet()
|
} ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
val referencedClassIds = referencedClasses.map { it.classSnapshot.getClassId() }.toSet()
|
val referencedClassIds = referencedClasses.mapTo(mutableSetOf()) { it.classId }
|
||||||
val transitivelyReferencedClassIds: Set<ClassId> =
|
val transitivelyReferencedClassIds: Set<ClassId> =
|
||||||
ImpactAnalysis.findImpactedClassesInclusive(referencedClassIds, supertypesResolver) // Use Set for presence check
|
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 <T> getLookupSymbols(fn: () -> T) = metrics?.measure(getLookupSymbols!!, fn) ?: fn()
|
||||||
|
fun <T> findReferencedClasses(fn: () -> T) = metrics?.measure(findReferencedClasses!!, fn) ?: fn()
|
||||||
|
fun <T> 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
|
* 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.
|
* duplicate classes, and then remove inaccessible classes.
|
||||||
*/
|
*/
|
||||||
internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<ClassSnapshotWithHash> {
|
internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<AccessibleClassSnapshot> {
|
||||||
return getNonDuplicateClassSnapshots().filter { it.classSnapshot !is InaccessibleClassSnapshot }
|
return getNonDuplicateClassSnapshots().filterIsInstance<AccessibleClassSnapshot>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,8 +159,8 @@ internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<Cla
|
|||||||
*
|
*
|
||||||
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
||||||
*/
|
*/
|
||||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshotWithHash> {
|
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshot> {
|
||||||
val classSnapshots = LinkedHashMap<String, ClassSnapshotWithHash>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
val classSnapshots = LinkedHashMap<String, ClassSnapshot>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||||
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
||||||
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
||||||
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
||||||
@@ -160,12 +174,12 @@ private sealed class ShrinkMode {
|
|||||||
object NoChanges : ShrinkMode()
|
object NoChanges : ShrinkMode()
|
||||||
|
|
||||||
class IncrementalNoNewLookups(
|
class IncrementalNoNewLookups(
|
||||||
val shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>,
|
val shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>,
|
||||||
) : ShrinkMode()
|
) : ShrinkMode()
|
||||||
|
|
||||||
class Incremental(
|
class Incremental(
|
||||||
val currentClasspathSnapshot: List<ClassSnapshotWithHash>,
|
val currentClasspathSnapshot: List<AccessibleClassSnapshot>,
|
||||||
val shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>,
|
val shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>,
|
||||||
val addedLookupSymbols: Set<LookupSymbolKey>
|
val addedLookupSymbols: Set<LookupSymbolKey>
|
||||||
) : ShrinkMode()
|
) : ShrinkMode()
|
||||||
|
|
||||||
@@ -175,8 +189,8 @@ private sealed class ShrinkMode {
|
|||||||
internal fun shrinkAndSaveClasspathSnapshot(
|
internal fun shrinkAndSaveClasspathSnapshot(
|
||||||
classpathChanges: ClasspathChanges.ClasspathSnapshotEnabled,
|
classpathChanges: ClasspathChanges.ClasspathSnapshotEnabled,
|
||||||
lookupStorage: LookupStorage,
|
lookupStorage: LookupStorage,
|
||||||
currentClasspathSnapshot: List<ClassSnapshotWithHash>?, // Not null iff classpathChanges is ToBeComputedByIncrementalCompiler
|
currentClasspathSnapshot: List<AccessibleClassSnapshot>?, // Not null iff classpathChanges is ToBeComputedByIncrementalCompiler
|
||||||
shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>?, // Same as above
|
shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>?, // Same as above
|
||||||
metrics: BuildMetricsReporter
|
metrics: BuildMetricsReporter
|
||||||
) {
|
) {
|
||||||
// In the following, we'll try to shrink the classpath snapshot incrementally when possible.
|
// In the following, we'll try to shrink the classpath snapshot incrementally when possible.
|
||||||
@@ -191,7 +205,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
|||||||
} else {
|
} else {
|
||||||
val shrunkPreviousClasspathAgainstPreviousLookups =
|
val shrunkPreviousClasspathAgainstPreviousLookups =
|
||||||
metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||||
ListExternalizer(ClassSnapshotWithHashExternalizer)
|
ListExternalizer(AccessibleClassSnapshotExternalizer)
|
||||||
.loadFromFile(classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile)
|
.loadFromFile(classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile)
|
||||||
}
|
}
|
||||||
ShrinkMode.Incremental(
|
ShrinkMode.Incremental(
|
||||||
@@ -219,7 +233,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shrink current classpath against current lookups
|
// Shrink current classpath against current lookups
|
||||||
val shrunkCurrentClasspath: List<ClassSnapshotWithHash>? = when (shrinkMode) {
|
val shrunkCurrentClasspath: List<AccessibleClassSnapshot>? = when (shrinkMode) {
|
||||||
is ShrinkMode.NoChanges -> null
|
is ShrinkMode.NoChanges -> null
|
||||||
is ShrinkMode.IncrementalNoNewLookups -> {
|
is ShrinkMode.IncrementalNoNewLookups -> {
|
||||||
// There are no new lookups, so
|
// There are no new lookups, so
|
||||||
@@ -227,13 +241,12 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
|||||||
shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups
|
shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups
|
||||||
}
|
}
|
||||||
is ShrinkMode.Incremental -> metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
is ShrinkMode.Incremental -> metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||||
val shrunkClasses = shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups.map { it.classSnapshot.getClassId() }.toSet()
|
val shrunkClasses = shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups.mapTo(mutableSetOf()) { it.classId }
|
||||||
val notYetShrunkClasses = shrinkMode.currentClasspathSnapshot.filter { it.classSnapshot.getClassId() !in shrunkClasses }
|
val notYetShrunkClasses = shrinkMode.currentClasspathSnapshot.filter { it.classId !in shrunkClasses }
|
||||||
val addedLookupSymbols = shrinkMode.addedLookupSymbols
|
val addedLookupSymbols = shrinkMode.addedLookupSymbols
|
||||||
.map { LookupSymbol(it.name, it.scope) }
|
.map { LookupSymbol(name = it.name, scope = it.scope) }
|
||||||
.filterLookupSymbols(shrinkMode.currentClasspathSnapshot.map { it.classSnapshot })
|
.filterLookupSymbols(notYetShrunkClasses)
|
||||||
// Don't provide a BuildMetricsReporter for the following call as the sub-BuildTimes in it have a different parent
|
val shrunkRemainingClassesAgainstNewLookups = shrinkClasses(notYetShrunkClasses, addedLookupSymbols)
|
||||||
val shrunkRemainingClassesAgainstNewLookups = shrink(notYetShrunkClasses, addedLookupSymbols)
|
|
||||||
|
|
||||||
shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups + shrunkRemainingClassesAgainstNewLookups
|
shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups + shrunkRemainingClassesAgainstNewLookups
|
||||||
}
|
}
|
||||||
@@ -244,8 +257,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
|||||||
.removeDuplicateAndInaccessibleClasses()
|
.removeDuplicateAndInaccessibleClasses()
|
||||||
}
|
}
|
||||||
metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
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
|
shrinkClasspath(classpathSnapshot, lookupStorage)
|
||||||
shrink(classpathSnapshot, lookupStorage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +269,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
metrics.measure(BuildTime.SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
metrics.measure(BuildTime.SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||||
ListExternalizer(ClassSnapshotWithHashExternalizer).saveToFile(
|
ListExternalizer(AccessibleClassSnapshotExternalizer).saveToFile(
|
||||||
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
||||||
shrunkCurrentClasspath!!
|
shrunkCurrentClasspath!!
|
||||||
)
|
)
|
||||||
|
|||||||
+29
-11
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.*
|
import org.jetbrains.kotlin.incremental.*
|
||||||
import org.jetbrains.kotlin.incremental.ChangesCollector.Companion.getNonPrivateMemberNames
|
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.load.kotlin.header.KotlinClassHeader.Kind.*
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -29,7 +31,7 @@ object ClasspathEntrySnapshotter {
|
|||||||
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
|
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())
|
val relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zip(snapshots).toMap(LinkedHashMap())
|
||||||
return ClasspathEntrySnapshot(relativePathsToSnapshotsMap)
|
return ClasspathEntrySnapshot(relativePathsToSnapshotsMap)
|
||||||
@@ -40,7 +42,11 @@ object ClasspathEntrySnapshotter {
|
|||||||
object ClassSnapshotter {
|
object ClassSnapshotter {
|
||||||
|
|
||||||
/** Creates [ClassSnapshot]s of the given classes. */
|
/** Creates [ClassSnapshot]s of the given classes. */
|
||||||
fun snapshot(classes: List<ClassFileWithContents>, includeDebugInfoInJavaSnapshot: Boolean? = null): List<ClassSnapshot> {
|
fun snapshot(
|
||||||
|
classes: List<ClassFileWithContents>,
|
||||||
|
granularity: ClassSnapshotGranularity = CLASS_MEMBER_LEVEL,
|
||||||
|
includeDebugInfoInJavaSnapshot: Boolean = false
|
||||||
|
): List<ClassSnapshot> {
|
||||||
// Find inaccessible classes first
|
// Find inaccessible classes first
|
||||||
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
|
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
|
||||||
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
|
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
|
||||||
@@ -48,23 +54,35 @@ object ClassSnapshotter {
|
|||||||
return classes.map {
|
return classes.map {
|
||||||
when {
|
when {
|
||||||
it.classInfo in inaccessibleClassesInfo -> InaccessibleClassSnapshot
|
it.classInfo in inaccessibleClassesInfo -> InaccessibleClassSnapshot
|
||||||
it.classInfo.isKotlinClass -> snapshotKotlinClass(it)
|
it.classInfo.isKotlinClass -> snapshotKotlinClass(it, granularity)
|
||||||
else -> JavaClassSnapshotter.snapshot(it, includeDebugInfoInJavaSnapshot)
|
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). */
|
/** 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 =
|
val kotlinClassInfo =
|
||||||
KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents)
|
KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents)
|
||||||
val packageMembers = when (kotlinClassInfo.classKind) {
|
val classId = kotlinClassInfo.classId
|
||||||
CLASS, MULTIFILE_CLASS -> null // See `KotlinClassSnapshot.packageMembers`'s kdoc
|
val classAbiHash = KotlinClassInfoExternalizer.toByteArray(kotlinClassInfo).md5()
|
||||||
else -> (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames().map {
|
val classMemberLevelSnapshot = kotlinClassInfo.takeIf { granularity == CLASS_MEMBER_LEVEL }
|
||||||
PackageMember(kotlinClassInfo.classId.packageFqName, it)
|
|
||||||
}
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+26
-8
@@ -54,24 +54,42 @@ object JavaClassChangesComputer {
|
|||||||
previousClassSnapshot: JavaClassSnapshot,
|
previousClassSnapshot: JavaClassSnapshot,
|
||||||
changes: ChangeSet.Collector
|
changes: ChangeSet.Collector
|
||||||
) {
|
) {
|
||||||
|
if (currentClassSnapshot.classAbiHash == previousClassSnapshot.classAbiHash) return
|
||||||
|
|
||||||
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
|
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
|
||||||
if (currentClassSnapshot.classAbiExcludingMembers.abiHash != previousClassSnapshot.classAbiExcludingMembers.abiHash) {
|
if (currentClassSnapshot.classMemberLevelSnapshot != null && previousClassSnapshot.classMemberLevelSnapshot != null) {
|
||||||
changes.addChangedClass(classId)
|
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 {
|
} else {
|
||||||
collectClassMemberChanges(classId, currentClassSnapshot.fieldsAbi, previousClassSnapshot.fieldsAbi, changes)
|
changes.addChangedClass(classId)
|
||||||
collectClassMemberChanges(classId, currentClassSnapshot.methodsAbi, previousClassSnapshot.methodsAbi, changes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collects changes between two lists of fields/methods within a class. */
|
/** Collects changes between two lists of fields/methods within a class. */
|
||||||
private fun collectClassMemberChanges(
|
private fun collectClassMemberChanges(
|
||||||
classId: ClassId,
|
classId: ClassId,
|
||||||
currentMemberSnapshots: List<AbiSnapshot>,
|
currentMemberSnapshots: List<JavaElementSnapshot>,
|
||||||
previousMemberSnapshots: List<AbiSnapshot>,
|
previousMemberSnapshots: List<JavaElementSnapshot>,
|
||||||
changes: ChangeSet.Collector
|
changes: ChangeSet.Collector
|
||||||
) {
|
) {
|
||||||
val currentMemberHashes: Map<Long, AbiSnapshot> = currentMemberSnapshots.associateBy { it.abiHash }
|
val currentMemberHashes: Map<Long, JavaElementSnapshot> = currentMemberSnapshots.associateBy { it.abiHash }
|
||||||
val previousMemberHashes: Map<Long, AbiSnapshot> = previousMemberSnapshots.associateBy { it.abiHash }
|
val previousMemberHashes: Map<Long, JavaElementSnapshot> = previousMemberSnapshots.associateBy { it.abiHash }
|
||||||
|
|
||||||
val addedMembers = currentMemberHashes.keys - previousMemberHashes.keys
|
val addedMembers = currentMemberHashes.keys - previousMemberHashes.keys
|
||||||
val removedMembers = previousMemberHashes.keys - currentMemberHashes.keys
|
val removedMembers = previousMemberHashes.keys - currentMemberHashes.keys
|
||||||
|
|||||||
+20
-7
@@ -6,7 +6,9 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder
|
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.md5
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
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. */
|
/** Computes a [JavaClassSnapshot] of a Java class. */
|
||||||
object JavaClassSnapshotter {
|
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.
|
// 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.
|
// It is acceptable to collect more info than required, but it is incorrect to collect less info than required.
|
||||||
// There are 2 approaches:
|
// There are 2 approaches:
|
||||||
@@ -52,9 +58,12 @@ object JavaClassSnapshotter {
|
|||||||
abiClass.methods.clear()
|
abiClass.methods.clear()
|
||||||
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
||||||
|
|
||||||
|
val detailedSnapshot = JavaClassMemberLevelSnapshot(classAbiExcludingMembers, fieldsAbi, methodsAbi)
|
||||||
return JavaClassSnapshot(
|
return JavaClassSnapshot(
|
||||||
classFile.classInfo.classId, classFile.classInfo.supertypes,
|
classId = classFile.classInfo.classId,
|
||||||
classAbiExcludingMembers, fieldsAbi, methodsAbi
|
classAbiHash = JavaClassMemberLevelSnapshotExternalizer.toByteArray(detailedSnapshot).md5(),
|
||||||
|
classMemberLevelSnapshot = detailedSnapshot.takeIf { granularity == CLASS_MEMBER_LEVEL },
|
||||||
|
supertypes = classFile.classInfo.supertypes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,15 +82,19 @@ object JavaClassSnapshotter {
|
|||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun snapshotJavaElement(javaElement: Any, javaElementName: String, includeDebugInfoInSnapshot: Boolean? = null): AbiSnapshot {
|
private fun snapshotJavaElement(
|
||||||
return if (includeDebugInfoInSnapshot == true) {
|
javaElement: Any,
|
||||||
|
javaElementName: String,
|
||||||
|
includeDebugInfoInSnapshot: Boolean
|
||||||
|
): JavaElementSnapshot {
|
||||||
|
return if (includeDebugInfoInSnapshot) {
|
||||||
val abiValue = gsonForDebug.toJson(javaElement)
|
val abiValue = gsonForDebug.toJson(javaElement)
|
||||||
val abiHash = abiValue.toByteArray().md5()
|
val abiHash = abiValue.toByteArray().md5()
|
||||||
AbiSnapshotForTests(javaElementName, abiHash, abiValue)
|
JavaElementSnapshotForTests(javaElementName, abiHash, abiValue)
|
||||||
} else {
|
} else {
|
||||||
val abiValue = gson.toJson(javaElement)
|
val abiValue = gson.toJson(javaElement)
|
||||||
val abiHash = abiValue.toByteArray().md5()
|
val abiHash = abiValue.toByteArray().md5()
|
||||||
AbiSnapshot(javaElementName, abiHash)
|
JavaElementSnapshot(javaElementName, abiHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-17
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
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.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
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()
|
data class PackageMember(val packageFqName: FqName, val memberName: String) : ProgramSymbol()
|
||||||
|
|
||||||
|
/** Compact representation for a set of [PackageMember]s. */
|
||||||
|
class PackageMemberSet(val packageToMembersMap: Map<FqName, Set<String>>) {
|
||||||
|
|
||||||
|
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<PackageMember>.compact(): PackageMemberSet {
|
||||||
|
val map = mutableMapOf<FqName, MutableSet<String>>()
|
||||||
|
forEach {
|
||||||
|
map.getOrPut(it.packageFqName) { mutableSetOf() }.add(it.memberName)
|
||||||
|
}
|
||||||
|
return PackageMemberSet(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<PackageMemberSet>.combine(): PackageMemberSet {
|
||||||
|
val combinedMap = mutableMapOf<FqName, MutableSet<String>>()
|
||||||
|
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
|
* Finds [LookupSymbol]s that potentially refer to classes on the given classpath, and returns the [ProgramSymbol]s corresponding to those
|
||||||
* [LookupSymbol]s.
|
* [LookupSymbol]s.
|
||||||
@@ -30,24 +62,23 @@ data class PackageMember(val packageFqName: FqName, val memberName: String) : Pr
|
|||||||
*
|
*
|
||||||
* The given classpath must not contain duplicate classes.
|
* 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<LookupSymbol>.filterLookupSymbols(classpath: List<ClassSnapshot>): List<ProgramSymbol> {
|
internal fun Collection<LookupSymbol>.filterLookupSymbols(classpath: List<AccessibleClassSnapshot>): List<ProgramSymbol> {
|
||||||
val (packageFacades, regularClasses) = classpath.partition {
|
val regularClassesOnClasspath: List<ClassId> = classpath.mapNotNull {
|
||||||
it is KotlinClassSnapshot && it.classInfo.classKind != KotlinClassHeader.Kind.CLASS
|
when (it) {
|
||||||
|
is RegularKotlinClassSnapshot -> it.classId
|
||||||
|
is JavaClassSnapshot -> it.classId
|
||||||
|
is PackageFacadeKotlinClassSnapshot, is MultifileClassKotlinClassSnapshot -> null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val regularClassesOnClasspath: List<ClassId> = regularClasses.map { it.getClassId() }
|
val packageMembersOnClasspath: PackageMemberSet = classpath.mapNotNull {
|
||||||
val packageMembersOnClasspath: Set<PackageMember> =
|
when (it) {
|
||||||
packageFacades.flatMap {
|
is RegularKotlinClassSnapshot, is JavaClassSnapshot -> null
|
||||||
if ((it as KotlinClassSnapshot).classInfo.classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS) {
|
is PackageFacadeKotlinClassSnapshot -> it.packageMembers
|
||||||
// If classKind == MULTIFILE_CLASS, we don't have the information about its package members (see
|
is MultifileClassKotlinClassSnapshot -> it.constants
|
||||||
// `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.
|
}.combine()
|
||||||
emptyList()
|
|
||||||
} else {
|
|
||||||
it.packageMembers!!
|
|
||||||
}
|
|
||||||
}.toSet() // Use Set for presence check
|
|
||||||
|
|
||||||
// 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
|
// 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
|
// have FqName `com.example.Foo`. ClassId `com/example/Foo` indicates class `Foo` in package `com/example', whereas ClassId
|
||||||
|
|||||||
+135
-22
@@ -8,20 +8,22 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
|||||||
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
||||||
import org.jetbrains.kotlin.incremental.ChangesEither
|
import org.jetbrains.kotlin.incremental.ChangesEither
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
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.ClassFileUtil.snapshot
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll
|
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.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.test.fail
|
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 {
|
abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||||
val testDataDir =
|
|
||||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
abstract fun testAbiVersusNonAbiChanges()
|
abstract fun testAbiVersusNonAbiChanges()
|
||||||
@@ -29,6 +31,12 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
|||||||
@Test
|
@Test
|
||||||
abstract fun testModifiedAddedRemovedElements()
|
abstract fun testModifiedAddedRemovedElements()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
abstract fun testModifiedAddedRemovedElements_ClassLevelSnapshot()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
abstract fun testMixedClassSnapshotGranularities()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
abstract fun testImpactAnalysis()
|
abstract fun testImpactAnalysis()
|
||||||
}
|
}
|
||||||
@@ -80,6 +88,49 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
).assertEquals(changes)
|
).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
|
@Test
|
||||||
override fun testImpactAnalysis() {
|
override fun testImpactAnalysis() {
|
||||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinOnly/src"), tmpDir)
|
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinOnly/src"), tmpDir)
|
||||||
@@ -230,6 +281,49 @@ class JavaOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
).assertEquals(changes)
|
).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
|
@Test
|
||||||
override fun testImpactAnalysis() {
|
override fun testImpactAnalysis() {
|
||||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir)
|
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir)
|
||||||
@@ -262,7 +356,7 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon()
|
|||||||
@Test
|
@Test
|
||||||
fun testImpactAnalysis() {
|
fun testImpactAnalysis() {
|
||||||
val changes =
|
val changes =
|
||||||
computeClasspathChanges(File(ClasspathChangesComputerTest.testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir)
|
computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir)
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
LookupSymbol(name = "changedProperty", scope = "com.example.ChangedKotlinSuperClass"),
|
LookupSymbol(name = "changedProperty", scope = "com.example.ChangedKotlinSuperClass"),
|
||||||
@@ -296,25 +390,44 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder): Changes {
|
private fun testMixedClassSnapshotGranularities_snapshotClasspath(
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
language: String, classpathSourceDirName: String, tmpDir: TemporaryFolder
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
): ClasspathSnapshot {
|
||||||
return computeClasspathChanges(currentSnapshot, previousSnapshot)
|
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 {
|
private fun List<ClassSnapshot>.toClasspathSnapshot(): ClasspathSnapshot {
|
||||||
val classpath = mutableListOf<File>()
|
val classpathEntrySnapshot = ClasspathEntrySnapshot(associateByTo(LinkedHashMap()) {
|
||||||
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
|
JvmClassName.byClassId((it as AccessibleClassSnapshot).classId).internalName + ".class"
|
||||||
val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir)
|
})
|
||||||
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
return ClasspathSnapshot(listOf(classpathEntrySnapshot))
|
||||||
|
}
|
||||||
|
|
||||||
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
private fun computeClasspathChanges(
|
||||||
val classSnapshots = classFiles.snapshot().map { it.withHash }
|
classpathSourceDir: File,
|
||||||
ClasspathEntrySnapshot(
|
tmpDir: TemporaryFolder,
|
||||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
granularity: ClassSnapshotGranularity? = null
|
||||||
)
|
): Changes {
|
||||||
}
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, granularity)
|
||||||
return ClasspathSnapshot(classpathEntrySnapshots)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, granularity)
|
||||||
|
return computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeClasspathChanges(
|
private fun computeClasspathChanges(
|
||||||
|
|||||||
+3
-3
@@ -36,8 +36,8 @@ class KotlinClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializer
|
|||||||
|
|
||||||
override val sourceFile = TestSourceFile(
|
override val sourceFile = TestSourceFile(
|
||||||
KotlinSourceFile(
|
KotlinSourceFile(
|
||||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt",
|
baseDir = File(testDataDir, "kotlin/testSimpleClass/src"), relativePath = "com/example/SimpleClass.kt",
|
||||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class")
|
preCompiledClassFile = ClassFile(File(testDataDir, "kotlin/testSimpleClass/classes"), "com/example/SimpleClass.class")
|
||||||
), tmpDir
|
), tmpDir
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTe
|
|||||||
|
|
||||||
override val sourceFile = TestSourceFile(
|
override val sourceFile = TestSourceFile(
|
||||||
JavaSourceFile(
|
JavaSourceFile(
|
||||||
baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java",
|
baseDir = File(testDataDir, "java/testSimpleClass/src"), relativePath = "com/example/SimpleClass.java",
|
||||||
), tmpDir
|
), tmpDir
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-9
@@ -6,14 +6,17 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder
|
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.asFile
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot
|
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.compile
|
||||||
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.ProcessBuilder.Redirect
|
||||||
|
|
||||||
abstract class ClasspathSnapshotTestCommon {
|
abstract class ClasspathSnapshotTestCommon {
|
||||||
|
|
||||||
@@ -68,12 +71,12 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
} else {
|
} else {
|
||||||
val srcDir = tmpDir.newFolder()
|
val srcDir = tmpDir.newFolder()
|
||||||
asFile().copyTo(File(srcDir, unixStyleRelativePath))
|
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. */
|
/** Compiles the source files in the given directory and returns all generated .class files. */
|
||||||
fun compileAll(srcDir: File, classpath: List<File>, tmpDir: TemporaryFolder): List<ClassFile> {
|
fun compileAll(srcDir: File, tmpDir: TemporaryFolder, classpath: List<File> = emptyList()): List<ClassFile> {
|
||||||
val kotlinClasses = compileKotlin(srcDir, classpath, tmpDir)
|
val kotlinClasses = compileKotlin(srcDir, classpath, tmpDir)
|
||||||
|
|
||||||
val javaClasspath = classpath + listOfNotNull(kotlinClasses.firstOrNull()?.classRoot)
|
val javaClasspath = classpath + listOfNotNull(kotlinClasses.firstOrNull()?.classRoot)
|
||||||
@@ -126,11 +129,20 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val classesDir = tmpDir.newFolder()
|
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,
|
srcDir.path,
|
||||||
classesDir,
|
"-d", classesDir.path,
|
||||||
extraClasspath = classpath.map { it.path }.toTypedArray()
|
"-classpath", (listOf(srcDir) + classpath).joinToString(File.pathSeparator) { it.path }
|
||||||
)
|
)
|
||||||
|
runCommandInNewProcess(commandAndArgs)
|
||||||
|
|
||||||
return getClassFilesInDir(classesDir)
|
return getClassFilesInDir(classesDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +176,52 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
|
|
||||||
fun ClassFile.readBytes() = asFile().readBytes()
|
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<ClassFile>.snapshot(): List<ClassSnapshot> {
|
fun List<ClassFile>.snapshot(granularity: ClassSnapshotGranularity? = null): List<ClassSnapshot> {
|
||||||
val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) }
|
val classes = map { ClassFileWithContents(it, it.readBytes()) }
|
||||||
return ClassSnapshotter.snapshot(classFilesWithContents)
|
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<File>()
|
||||||
|
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<String>) {
|
||||||
|
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(" ")}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+129
-64
@@ -6,94 +6,159 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.readBytes
|
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.JavaSourceFile
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
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.assertEquals
|
||||||
import org.junit.Assert.assertNotEquals
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import java.io.File
|
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 {
|
class KotlinOnlyClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||||
val testDataDir =
|
|
||||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract val sourceFile: TestSourceFile
|
@Suppress("SameParameterValue")
|
||||||
protected abstract val sourceFileWithAbiChange: TestSourceFile
|
private fun getSourceFile(testName: String, relativePath: String) = TestSourceFile(
|
||||||
protected abstract val sourceFileWithNonAbiChange: TestSourceFile
|
KotlinSourceFile(
|
||||||
|
baseDir = File("$testDataDir/kotlin/$testName/src"), relativePath = relativePath,
|
||||||
private val expectedSnapshotFile: File
|
preCompiledClassFile = ClassFile(File("$testDataDir/kotlin/$testName/classes"), relativePath.replace(".kt", ".class"))
|
||||||
get() = sourceFile.asFile().let {
|
), tmpDir
|
||||||
val srcDir = File(it.path.substringBeforeLast("src") + "src")
|
)
|
||||||
val relativePath = it.relativeTo(srcDir)
|
|
||||||
testDataDir.resolve("expected-snapshot").resolve(relativePath.parent).resolve(relativePath.nameWithoutExtension + ".json")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
fun testSimpleClass() {
|
||||||
val classSnapshot = sourceFile.compileSingle().let {
|
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.kt")
|
||||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInJavaSnapshot = true)
|
val actualSnapshot = sourceFile.compileAndSnapshot().toGson()
|
||||||
}.single()
|
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
|
@Test
|
||||||
fun `test ClassSnapshotter extracts ABI info from a class`() {
|
fun testSimpleClass_ClassLevelSnapshot() {
|
||||||
// After an ABI change, the snapshot must change
|
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.kt")
|
||||||
assertNotEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithAbiChange.compileAndSnapshot().toGson())
|
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
|
@Test
|
||||||
fun `test ClassSnapshotter does not extract non-ABI info from a class`() {
|
fun testPackageFacadeClasses() {
|
||||||
// After a non-ABI change, the snapshot must not change
|
val classpathSnapshot = snapshotClasspath(File("$testDataDir/kotlin/testPackageFacadeClasses/src"), tmpDir)
|
||||||
assertEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithNonAbiChange.compileAndSnapshot().toGson())
|
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(
|
@Suppress("SameParameterValue")
|
||||||
KotlinSourceFile(
|
private fun getSourceFile(testName: String, relativePath: String) = TestSourceFile(
|
||||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt",
|
JavaSourceFile(baseDir = File("$testDataDir/java/$testName/src"), relativePath = relativePath), tmpDir
|
||||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class")
|
|
||||||
), tmpDir
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override val sourceFileWithAbiChange = TestSourceFile(
|
private fun TestSourceFile.compileAndSnapshotWithDebugInfo(): ClassSnapshot {
|
||||||
KotlinSourceFile(
|
val classFile = compileSingle()
|
||||||
baseDir = File(testDataDir, "src-changed/kotlin/abi-change"), relativePath = "com/example/SimpleClass.kt",
|
return ClassSnapshotter.snapshot(
|
||||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotllin/abi-change"), "com/example/SimpleClass.class")
|
listOf(ClassFileWithContents(classFile, classFile.readBytes())),
|
||||||
), tmpDir
|
includeDebugInfoInJavaSnapshot = true
|
||||||
)
|
).single()
|
||||||
|
}
|
||||||
|
|
||||||
override val sourceFileWithNonAbiChange = TestSourceFile(
|
@Test
|
||||||
KotlinSourceFile(
|
fun testSimpleClass() {
|
||||||
baseDir = File(testDataDir, "src-changed/kotlin/non-abi-change"), relativePath = "com/example/SimpleClass.kt",
|
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.java")
|
||||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotlin/non-abi-change"), "com/example/SimpleClass.class")
|
val actualSnapshot = sourceFile.compileAndSnapshotWithDebugInfo().toGson()
|
||||||
), tmpDir
|
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() {
|
private fun TestSourceFile.getExpectedSnapshotFile(granularity: ClassSnapshotGranularity? = null): File {
|
||||||
|
val relativePath = sourceFile.unixStyleRelativePath.substringBeforeLast(".") + ".json"
|
||||||
override val sourceFile = TestSourceFile(
|
val expectedSnapshotDirName = if (granularity == null) "expected-snapshot" else "expected-snapshot-${granularity.name}"
|
||||||
JavaSourceFile(
|
return sourceFile.baseDir.resolve("../$expectedSnapshotDirName/$relativePath")
|
||||||
baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java",
|
}
|
||||||
), tmpDir
|
|
||||||
)
|
private fun String.assertContains(vararg elements: String) {
|
||||||
|
elements.forEach {
|
||||||
override val sourceFileWithAbiChange = TestSourceFile(
|
assertTrue(contains(it))
|
||||||
JavaSourceFile(
|
}
|
||||||
baseDir = File(testDataDir, "src-changed/java/abi-change"), relativePath = "com/example/SimpleClass.java",
|
}
|
||||||
), tmpDir
|
|
||||||
)
|
private fun String.assertDoesNotContain(vararg elements: String) {
|
||||||
|
elements.forEach {
|
||||||
override val sourceFileWithNonAbiChange = TestSourceFile(
|
assertFalse(contains(it))
|
||||||
JavaSourceFile(
|
}
|
||||||
baseDir = File(testDataDir, "src-changed/java/non-abi-change"), relativePath = "com/example/SimpleClass.java",
|
|
||||||
), tmpDir
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
@@ -1,5 +1,6 @@
|
|||||||
package com.example
|
package com.example
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
class SomeClass {
|
class SomeClass {
|
||||||
|
|
||||||
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
package com.example
|
package com.example
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
class SomeClass {
|
class SomeClass {
|
||||||
|
|
||||||
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public long modifiedField = 0;
|
||||||
|
|
||||||
|
public long modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public long modifiedField = 0;
|
||||||
|
|
||||||
|
public long modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public long modifiedField = 0;
|
||||||
|
|
||||||
|
public long modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public long modifiedField = 0;
|
||||||
|
|
||||||
|
public long modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public int modifiedField = 0;
|
||||||
|
|
||||||
|
public int modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public int modifiedField = 0;
|
||||||
|
|
||||||
|
public int modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public int modifiedField = 0;
|
||||||
|
|
||||||
|
public int modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
|
||||||
|
public int modifiedField = 0;
|
||||||
|
|
||||||
|
public int modifiedMethod() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Long = 0
|
||||||
|
fun modifiedFunction(): Long = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Long = 0
|
||||||
|
fun modifiedFunction(): Long = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Long = 0
|
||||||
|
fun modifiedFunction(): Long = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Long = 0
|
||||||
|
fun modifiedFunction(): Long = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Int = 0
|
||||||
|
fun modifiedFunction(): Int = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Int = 0
|
||||||
|
fun modifiedFunction(): Int = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Int = 0
|
||||||
|
fun modifiedFunction(): Int = 0
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||||
|
val modifiedProperty: Int = 0
|
||||||
|
fun modifiedFunction(): Int = 0
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.example
|
package com.example
|
||||||
|
|
||||||
@java.lang.Deprecated // Changed annotation
|
@kotlin.Deprecated("") // Changed annotation
|
||||||
class ModifiedClassUnchangedMembers {
|
class ModifiedClassUnchangedMembers {
|
||||||
val unchangedProperty = 0
|
val unchangedProperty = 0
|
||||||
fun unchangedFunction() {}
|
fun unchangedFunction() {}
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-52
@@ -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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"classId": {
|
||||||
|
"packageFqName": {
|
||||||
|
"fqName": {
|
||||||
|
"fqName": "com.example"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relativeClassName": {
|
||||||
|
"fqName": {
|
||||||
|
"fqName": "SimpleClass"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local": false
|
||||||
|
},
|
||||||
|
"classAbiHash": 2419143040117344894,
|
||||||
|
"supertypes": [
|
||||||
|
{
|
||||||
|
"internalName": "java/lang/Object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+47
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+16
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
val propertyInFileFacade = "propertyInFileFacade's value"
|
||||||
|
fun functionInFileFacade() = "functionInFileFacade's body"
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
@file:JvmName("MultifileClass")
|
||||||
|
@file:JvmMultifileClass
|
||||||
|
|
||||||
|
package com.example
|
||||||
|
|
||||||
|
val propertyInMultifileClass1 = "propertyInMultifileClass1's value"
|
||||||
|
fun functionInMultifileClass1() = "functionInMultifileClass1's body"
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
@file:JvmName("MultifileClass")
|
||||||
|
@file:JvmMultifileClass
|
||||||
|
|
||||||
|
package com.example
|
||||||
|
|
||||||
|
val propertyInMultifileClass2 = "propertyInMultifileClass2's value"
|
||||||
|
fun functionInMultifileClass2() = "functionInMultifileClass2's body"
|
||||||
BIN
Binary file not shown.
+21
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"classId": {
|
||||||
|
"packageFqName": {
|
||||||
|
"fqName": {
|
||||||
|
"fqName": "com.example"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relativeClassName": {
|
||||||
|
"fqName": {
|
||||||
|
"fqName": "SimpleClass"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local": false
|
||||||
|
},
|
||||||
|
"classAbiHash": -2605231127310346403,
|
||||||
|
"supertypes": [
|
||||||
|
{
|
||||||
|
"internalName": "java/lang/Object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+18
-8
@@ -1,5 +1,19 @@
|
|||||||
{
|
{
|
||||||
"classInfo": {
|
"classId": {
|
||||||
|
"packageFqName": {
|
||||||
|
"fqName": {
|
||||||
|
"fqName": "com.example"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relativeClassName": {
|
||||||
|
"fqName": {
|
||||||
|
"fqName": "SimpleClass"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local": false
|
||||||
|
},
|
||||||
|
"classAbiHash": -2605231127310346403,
|
||||||
|
"classMemberLevelSnapshot": {
|
||||||
"classId": {
|
"classId": {
|
||||||
"packageFqName": {
|
"packageFqName": {
|
||||||
"fqName": {
|
"fqName": {
|
||||||
@@ -15,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"classKind": "CLASS",
|
"classKind": "CLASS",
|
||||||
"classHeaderData": [
|
"classHeaderData": [
|
||||||
"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0005\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\b\u001a\u00020\u0004H\u0002J\u0006\u0010\t\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004XD¢\u0006\u0002\n\u0000R\u0014\u0010\u0005\u001a\u00020\u0004XD¢\u0006\b\n\u0000\u001a\u0004\b\u0006\u0010\u0007"
|
"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0005\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\b\u001a\u00020\u0004H\u0002J\u0006\u0010\t\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004XD¢\u0006\u0002\n\u0000R\u0014\u0010\u0005\u001a\u00020\u0004XD¢\u0006\b\n\u0000\u001a\u0004\b\u0006\u0010\u0007"
|
||||||
],
|
],
|
||||||
"classHeaderStrings": [
|
"classHeaderStrings": [
|
||||||
"Lcom/example/SimpleClass;",
|
"Lcom/example/SimpleClass;",
|
||||||
@@ -25,7 +39,7 @@
|
|||||||
"",
|
"",
|
||||||
"publicProperty",
|
"publicProperty",
|
||||||
"getPublicProperty",
|
"getPublicProperty",
|
||||||
"()I",
|
"()Ljava/lang/String;",
|
||||||
"privateFunction",
|
"privateFunction",
|
||||||
"publicFunction"
|
"publicFunction"
|
||||||
],
|
],
|
||||||
@@ -48,9 +62,5 @@
|
|||||||
{
|
{
|
||||||
"internalName": "java/lang/Object"
|
"internalName": "java/lang/Object"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"withHash$delegate": {
|
|
||||||
"initializer": {},
|
|
||||||
"_value": {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example
|
||||||
|
|
||||||
|
class SimpleClass {
|
||||||
|
|
||||||
|
val publicProperty: String = "publicProperty's value"
|
||||||
|
|
||||||
|
private val privateProperty: String = "privateProperty's value"
|
||||||
|
|
||||||
|
fun publicFunction(): String = "publicFunction's body"
|
||||||
|
|
||||||
|
private fun privateFunction(): String = "privateFunction's body"
|
||||||
|
}
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
package com.example;
|
|
||||||
|
|
||||||
class SimpleClass {
|
|
||||||
|
|
||||||
public long publicField = 0;
|
|
||||||
|
|
||||||
private long privateField = 0;
|
|
||||||
|
|
||||||
public long publicMethod() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long privateMethod() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
package com.example;
|
|
||||||
|
|
||||||
class SimpleClass {
|
|
||||||
|
|
||||||
public int publicField = 1000;
|
|
||||||
|
|
||||||
private int privateField = 1000;
|
|
||||||
|
|
||||||
public int publicMethod() {
|
|
||||||
return 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int privateMethod() {
|
|
||||||
return 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
package com.example
|
|
||||||
|
|
||||||
class SimpleClass {
|
|
||||||
|
|
||||||
val publicProperty: Long = 0
|
|
||||||
|
|
||||||
private val privateProperty: Long = 0
|
|
||||||
|
|
||||||
fun publicFunction(): Long = 0
|
|
||||||
|
|
||||||
private fun privateFunction(): Long = 0
|
|
||||||
}
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
package com.example
|
|
||||||
|
|
||||||
class SimpleClass {
|
|
||||||
|
|
||||||
val publicProperty: Int = 1000
|
|
||||||
|
|
||||||
private val privateProperty: Int = 1000
|
|
||||||
|
|
||||||
fun publicFunction(): Int = 1000
|
|
||||||
|
|
||||||
private fun privateFunction(): Int = 1000
|
|
||||||
}
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
package com.example;
|
|
||||||
|
|
||||||
class SimpleClass {
|
|
||||||
|
|
||||||
public int publicField = 0;
|
|
||||||
|
|
||||||
private int privateField = 0;
|
|
||||||
|
|
||||||
public int publicMethod() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int privateMethod() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
package com.example
|
|
||||||
|
|
||||||
class SimpleClass {
|
|
||||||
|
|
||||||
val publicProperty: Int = 0
|
|
||||||
|
|
||||||
private val privateProperty: Int = 0
|
|
||||||
|
|
||||||
fun publicFunction(): Int = 0
|
|
||||||
|
|
||||||
private fun privateFunction(): Int = 0
|
|
||||||
}
|
|
||||||
+2
-2
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
|
|||||||
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
|
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
|
||||||
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
||||||
import org.jetbrains.kotlin.incremental.cleanDirectoryContents
|
import org.jetbrains.kotlin.incremental.cleanDirectoryContents
|
||||||
import org.jetbrains.kotlin.incremental.forceDeleteRecursively
|
import org.jetbrains.kotlin.incremental.deleteRecursivelyOrThrow
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun throwGradleExceptionIfError(
|
fun throwGradleExceptionIfError(
|
||||||
@@ -63,7 +63,7 @@ internal fun cleanOutputsAndLocalState(
|
|||||||
}
|
}
|
||||||
file.isFile -> {
|
file.isFile -> {
|
||||||
log.debug("Deleting output file: $file")
|
log.debug("Deleting output file: $file")
|
||||||
file.forceDeleteRecursively()
|
file.deleteRecursivelyOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user